From 4f66a9549a393e4d74b93eb85301a04ea94bc750 Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Wed, 24 Jul 2019 15:55:17 +0800
Subject: [PATCH 01/16] Add duktape as javascript engine.

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 configure.ac                                  |   28 +-
 src/polkitbackend/Makefile.am                 |   14 +-
 .../polkitbackendduktapeauthority.c           | 1402 +++++++++++++++++
 3 files changed, 1436 insertions(+), 8 deletions(-)
 create mode 100644 src/polkitbackend/polkitbackendduktapeauthority.c

diff --git a/configure.ac b/configure.ac
index e434ca2..5a03593 100644
--- a/configure.ac
+++ b/configure.ac
@@ -80,11 +80,22 @@ PKG_CHECK_MODULES(GLIB, [gmodule-2.0 gio-unix-2.0 >= 2.30.0])
 AC_SUBST(GLIB_CFLAGS)
 AC_SUBST(GLIB_LIBS)
 
-PKG_CHECK_MODULES(LIBJS, [mozjs-78])
-
-AC_SUBST(LIBJS_CFLAGS)
-AC_SUBST(LIBJS_CXXFLAGS)
-AC_SUBST(LIBJS_LIBS)
+dnl ---------------------------------------------------------------------------
+dnl - Check javascript backend
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(duktape, AS_HELP_STRING([--with-duktape],[Use Duktape as javascript backend]),with_duktape=yes,with_duktape=no)
+AS_IF([test x${with_duktape} == xyes], [
+  PKG_CHECK_MODULES(LIBJS, [duktape >= 2.0.0 ])
+  AC_SUBST(LIBJS_CFLAGS)
+  AC_SUBST(LIBJS_LIBS)
+], [
+  PKG_CHECK_MODULES(LIBJS, [mozjs-78])
+
+  AC_SUBST(LIBJS_CFLAGS)
+  AC_SUBST(LIBJS_CXXFLAGS)
+  AC_SUBST(LIBJS_LIBS)
+])
+AM_CONDITIONAL(USE_DUKTAPE, [test x$with_duktape == xyes], [Using duktape as javascript engine library])
 
 EXPAT_LIB=""
 AC_ARG_WITH(expat, [  --with-expat=<dir>      Use expat from here],
@@ -585,6 +596,13 @@ echo "
         PAM support:                ${have_pam}
         systemdsystemunitdir:       ${systemdsystemunitdir}
         polkitd user:               ${POLKITD_USER}"
+if test "x${with_duktape}" = xyes; then
+echo "
+        Javascript engine:          Duktape"
+else
+echo "
+        Javascript engine:          Mozjs"
+fi
 
 if test "$have_pam" = yes ; then
 echo "
diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am
index 7e3c080..abcbc6f 100644
--- a/src/polkitbackend/Makefile.am
+++ b/src/polkitbackend/Makefile.am
@@ -33,7 +33,7 @@ libpolkit_backend_1_la_SOURCES =                                   			\
 	polkitbackendprivate.h								\
 	polkitbackendauthority.h		polkitbackendauthority.c		\
 	polkitbackendinteractiveauthority.h	polkitbackendinteractiveauthority.c	\
-	polkitbackendjsauthority.h		polkitbackendjsauthority.cpp		\
+	polkitbackendjsauthority.h				\
 	polkitbackendactionpool.h		polkitbackendactionpool.c		\
 	polkitbackendactionlookup.h		polkitbackendactionlookup.c		\
         $(NULL)
@@ -51,19 +51,27 @@ libpolkit_backend_1_la_CFLAGS =                                        	\
         -D_POLKIT_BACKEND_COMPILATION                                  	\
         $(GLIB_CFLAGS)							\
 	$(LIBSYSTEMD_CFLAGS)						\
-	$(LIBJS_CFLAGS)							\
+	$(LIBJS_CFLAGS)						\
         $(NULL)
 
 libpolkit_backend_1_la_CXXFLAGS = $(libpolkit_backend_1_la_CFLAGS)
 
 libpolkit_backend_1_la_LIBADD =                               		\
         $(GLIB_LIBS)							\
+        $(DUKTAPE_LIBS)							\
 	$(LIBSYSTEMD_LIBS)						\
 	$(top_builddir)/src/polkit/libpolkit-gobject-1.la		\
 	$(EXPAT_LIBS)							\
-	$(LIBJS_LIBS)							\
+	$(LIBJS_LIBS)                                                   \
         $(NULL)
 
+if USE_DUKTAPE
+libpolkit_backend_1_la_SOURCES += polkitbackendduktapeauthority.c
+libpolkit_backend_1_la_LIBADD += -lm
+else
+libpolkit_backend_1_la_SOURCES += polkitbackendjsauthority.cpp
+endif
+
 rulesdir = $(sysconfdir)/polkit-1/rules.d
 rules_DATA = 50-default.rules
 
diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
new file mode 100644
index 0000000..ae98453
--- /dev/null
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -0,0 +1,1402 @@
+/*
+ * Copyright (C) 2008-2012 Red Hat, Inc.
+ * Copyright (C) 2015 Tangent Space <jstpierre@mecheye.net>
+ * Copyright (C) 2019 Wu Xiaotian <yetist@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+#include <sys/wait.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netdb.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#include <locale.h>
+#include <glib/gi18n-lib.h>
+
+#include <polkit/polkit.h>
+#include "polkitbackendjsauthority.h"
+
+#include <polkit/polkitprivate.h>
+
+#ifdef HAVE_LIBSYSTEMD
+#include <systemd/sd-login.h>
+#endif /* HAVE_LIBSYSTEMD */
+
+#include "initjs.h" /* init.js */
+#include "duktape.h"
+
+/**
+ * SECTION:polkitbackendjsauthority
+ * @title: PolkitBackendJsAuthority
+ * @short_description: JS Authority
+ * @stability: Unstable
+ *
+ * An implementation of #PolkitBackendAuthority that reads and
+ * evalates Javascript files and supports interaction with
+ * authentication agents (virtue of being based on
+ * #PolkitBackendInteractiveAuthority).
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _PolkitBackendJsAuthorityPrivate
+{
+  gchar **rules_dirs;
+  GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */
+  duk_context *cx;
+};
+
+#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND)
+
+static void utils_spawn (const gchar *const  *argv,
+                         guint                timeout_seconds,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data);
+
+gboolean utils_spawn_finish (GAsyncResult   *res,
+                             gint           *out_exit_status,
+                             gchar         **out_standard_output,
+                             gchar         **out_standard_error,
+                             GError        **error);
+
+static void on_dir_monitor_changed (GFileMonitor     *monitor,
+                                    GFile            *file,
+                                    GFile            *other_file,
+                                    GFileMonitorEvent event_type,
+                                    gpointer          user_data);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+enum
+{
+  PROP_0,
+  PROP_RULES_DIRS,
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority,
+                                                                     PolkitSubject                     *caller,
+                                                                     PolkitSubject                     *subject,
+                                                                     PolkitIdentity                    *user_for_subject,
+                                                                     gboolean                           subject_is_local,
+                                                                     gboolean                           subject_is_active,
+                                                                     const gchar                       *action_id,
+                                                                     PolkitDetails                     *details);
+
+static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync (
+                                                          PolkitBackendInteractiveAuthority *authority,
+                                                          PolkitSubject                     *caller,
+                                                          PolkitSubject                     *subject,
+                                                          PolkitIdentity                    *user_for_subject,
+                                                          gboolean                           subject_is_local,
+                                                          gboolean                           subject_is_active,
+                                                          const gchar                       *action_id,
+                                                          PolkitDetails                     *details,
+                                                          PolkitImplicitAuthorization        implicit);
+
+G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
+{
+  authority->priv = G_TYPE_INSTANCE_GET_PRIVATE (authority,
+                                                 POLKIT_BACKEND_TYPE_JS_AUTHORITY,
+                                                 PolkitBackendJsAuthorityPrivate);
+}
+
+static gint
+rules_file_name_cmp (const gchar *a,
+                     const gchar *b)
+{
+  gint ret;
+  const gchar *a_base;
+  const gchar *b_base;
+
+  a_base = strrchr (a, '/');
+  b_base = strrchr (b, '/');
+
+  g_assert (a_base != NULL);
+  g_assert (b_base != NULL);
+  a_base += 1;
+  b_base += 1;
+
+  ret = g_strcmp0 (a_base, b_base);
+  if (ret == 0)
+    {
+      /* /etc wins over /usr */
+      ret = g_strcmp0 (a, b);
+      g_assert (ret != 0);
+    }
+
+  return ret;
+}
+
+static void
+load_scripts (PolkitBackendJsAuthority  *authority)
+{
+  duk_context *cx = authority->priv->cx;
+  GList *files = NULL;
+  GList *l;
+  guint num_scripts = 0;
+  GError *error = NULL;
+  guint n;
+
+  files = NULL;
+
+  for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++)
+    {
+      const gchar *dir_name = authority->priv->rules_dirs[n];
+      GDir *dir = NULL;
+
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Loading rules from directory %s",
+                                    dir_name);
+
+      dir = g_dir_open (dir_name,
+                        0,
+                        &error);
+      if (dir == NULL)
+        {
+          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                        "Error opening rules directory: %s (%s, %d)",
+                                        error->message, g_quark_to_string (error->domain), error->code);
+          g_clear_error (&error);
+        }
+      else
+        {
+          const gchar *name;
+          while ((name = g_dir_read_name (dir)) != NULL)
+            {
+              if (g_str_has_suffix (name, ".rules"))
+                files = g_list_prepend (files, g_strdup_printf ("%s/%s", dir_name, name));
+            }
+          g_dir_close (dir);
+        }
+    }
+
+  files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp);
+
+  for (l = files; l != NULL; l = l->next)
+    {
+      const gchar *filename = l->data;
+
+#if (DUK_VERSION >= 20000)
+      gchar *contents;
+      gsize length;
+      GError *error = NULL;
+      if (!g_file_get_contents (filename, &contents, &length, &error)){
+        g_warning("Error when file contents of %s: %s\n", filename, error->message);
+        g_error_free (error);
+        continue;
+      }
+      if (duk_peval_lstring_noresult(cx, contents,length) != 0)
+#else
+      if (duk_peval_file_noresult (cx, filename) != 0)
+#endif
+        {
+          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                        "Error compiling script %s: %s",
+                                        filename, duk_safe_to_string (authority->priv->cx, -1));
+#if (DUK_VERSION >= 20000)
+          g_free (contents);
+#endif
+          continue;
+        }
+#if (DUK_VERSION >= 20000)
+      g_free (contents);
+#endif
+      num_scripts++;
+    }
+
+  polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                "Finished loading, compiling and executing %d rules",
+                                num_scripts);
+  g_list_free_full (files, g_free);
+}
+
+static void
+reload_scripts (PolkitBackendJsAuthority *authority)
+{
+  duk_context *cx = authority->priv->cx;
+
+  duk_set_top (cx, 0);
+  duk_get_global_string (cx, "polkit");
+  duk_push_string (cx, "_deleteRules");
+
+  duk_call_prop (cx, 0, 0);
+
+  polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                "Collecting garbage unconditionally...");
+
+  load_scripts (authority);
+
+  /* Let applications know we have new rules... */
+  g_signal_emit_by_name (authority, "changed");
+}
+
+static void
+on_dir_monitor_changed (GFileMonitor     *monitor,
+                        GFile            *file,
+                        GFile            *other_file,
+                        GFileMonitorEvent event_type,
+                        gpointer          user_data)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
+
+  /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution?
+   *       Because when editing a file with emacs we get 4-8 events..
+   */
+
+  if (file != NULL)
+    {
+      gchar *name;
+
+      name = g_file_get_basename (file);
+
+      /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */
+      if (!g_str_has_prefix (name, ".") &&
+          !g_str_has_prefix (name, "#") &&
+          g_str_has_suffix (name, ".rules") &&
+          (event_type == G_FILE_MONITOR_EVENT_CREATED ||
+           event_type == G_FILE_MONITOR_EVENT_DELETED ||
+           event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT))
+        {
+          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                        "Reloading rules");
+          reload_scripts (authority);
+        }
+      g_free (name);
+    }
+}
+
+
+static void
+setup_file_monitors (PolkitBackendJsAuthority *authority)
+{
+  guint n;
+  GPtrArray *p;
+
+  p = g_ptr_array_new ();
+  for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++)
+    {
+      GFile *file;
+      GError *error;
+      GFileMonitor *monitor;
+
+      file = g_file_new_for_path (authority->priv->rules_dirs[n]);
+      error = NULL;
+      monitor = g_file_monitor_directory (file,
+                                          G_FILE_MONITOR_NONE,
+                                          NULL,
+                                          &error);
+      g_object_unref (file);
+      if (monitor == NULL)
+        {
+          g_warning ("Error monitoring directory %s: %s",
+                     authority->priv->rules_dirs[n],
+                     error->message);
+          g_clear_error (&error);
+        }
+      else
+        {
+          g_signal_connect (monitor,
+                            "changed",
+                            G_CALLBACK (on_dir_monitor_changed),
+                            authority);
+          g_ptr_array_add (p, monitor);
+        }
+    }
+  g_ptr_array_add (p, NULL);
+  authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE);
+}
+
+static duk_ret_t js_polkit_log (duk_context *cx);
+static duk_ret_t js_polkit_spawn (duk_context *cx);
+static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx);
+
+static const duk_function_list_entry js_polkit_functions[] =
+{
+  { "log", js_polkit_log, 1 },
+  { "spawn", js_polkit_spawn, 1 },
+  { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 },
+  { NULL, NULL, 0 },
+};
+
+static void
+polkit_backend_js_authority_constructed (GObject *object)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
+  duk_context *cx;
+
+  cx = duk_create_heap (NULL, NULL, NULL, authority, NULL);
+  if (cx == NULL)
+    goto fail;
+
+  authority->priv->cx = cx;
+
+  duk_push_global_object (cx);
+  duk_push_object (cx);
+  duk_put_function_list (cx, -1, js_polkit_functions);
+  duk_put_prop_string (cx, -2, "polkit");
+
+  duk_eval_string (cx, init_js);
+
+  if (authority->priv->rules_dirs == NULL)
+    {
+      authority->priv->rules_dirs = g_new0 (gchar *, 3);
+      authority->priv->rules_dirs[0] = g_strdup (PACKAGE_SYSCONF_DIR "/polkit-1/rules.d");
+      authority->priv->rules_dirs[1] = g_strdup (PACKAGE_DATA_DIR "/polkit-1/rules.d");
+    }
+
+  setup_file_monitors (authority);
+  load_scripts (authority);
+
+  G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object);
+  return;
+
+ fail:
+  g_critical ("Error initializing JavaScript environment");
+  g_assert_not_reached ();
+}
+
+static void
+polkit_backend_js_authority_finalize (GObject *object)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
+  guint n;
+
+  for (n = 0; authority->priv->dir_monitors != NULL && authority->priv->dir_monitors[n] != NULL; n++)
+    {
+      GFileMonitor *monitor = authority->priv->dir_monitors[n];
+      g_signal_handlers_disconnect_by_func (monitor,
+                                            G_CALLBACK (on_dir_monitor_changed),
+                                            authority);
+      g_object_unref (monitor);
+    }
+  g_free (authority->priv->dir_monitors);
+  g_strfreev (authority->priv->rules_dirs);
+
+  duk_destroy_heap (authority->priv->cx);
+
+  G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object);
+}
+
+static void
+polkit_backend_js_authority_set_property (GObject      *object,
+                                          guint         property_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
+
+  switch (property_id)
+    {
+      case PROP_RULES_DIRS:
+        g_assert (authority->priv->rules_dirs == NULL);
+        authority->priv->rules_dirs = (gchar **) g_value_dup_boxed (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static const gchar *
+polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority)
+{
+  return "js";
+}
+
+static const gchar *
+polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority)
+{
+  return PACKAGE_VERSION;
+}
+
+static PolkitAuthorityFeatures
+polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority)
+{
+  return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION;
+}
+
+static void
+polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass)
+{
+  GObjectClass *gobject_class;
+  PolkitBackendAuthorityClass *authority_class;
+  PolkitBackendInteractiveAuthorityClass *interactive_authority_class;
+
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize                               = polkit_backend_js_authority_finalize;
+  gobject_class->set_property                           = polkit_backend_js_authority_set_property;
+  gobject_class->constructed                            = polkit_backend_js_authority_constructed;
+
+  authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass);
+  authority_class->get_name                             = polkit_backend_js_authority_get_name;
+  authority_class->get_version                          = polkit_backend_js_authority_get_version;
+  authority_class->get_features                         = polkit_backend_js_authority_get_features;
+
+  interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass);
+  interactive_authority_class->get_admin_identities     = polkit_backend_js_authority_get_admin_auth_identities;
+  interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_RULES_DIRS,
+                                   g_param_spec_boxed ("rules-dirs",
+                                                       NULL,
+                                                       NULL,
+                                                       G_TYPE_STRV,
+                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+
+  g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+set_property_str (duk_context *cx,
+                  const gchar *name,
+                  const gchar *value)
+{
+  duk_push_string (cx, value);
+  duk_put_prop_string (cx, -2, name);
+}
+
+static void
+set_property_strv (duk_context *cx,
+                   const gchar *name,
+                   GPtrArray   *value)
+{
+  guint n;
+  duk_push_array (cx);
+  for (n = 0; n < value->len; n++)
+    {
+      duk_push_string (cx, g_ptr_array_index (value, n));
+      duk_put_prop_index (cx, -2, n);
+    }
+  duk_put_prop_string (cx, -2, name);
+}
+
+static void
+set_property_int32 (duk_context *cx,
+                    const gchar *name,
+                    gint32       value)
+{
+  duk_push_int (cx, value);
+  duk_put_prop_string (cx, -2, name);
+}
+
+static void
+set_property_bool (duk_context *cx,
+                   const char  *name,
+                   gboolean     value)
+{
+  duk_push_boolean (cx, value);
+  duk_put_prop_string (cx, -2, name);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+push_subject (duk_context               *cx,
+              PolkitSubject             *subject,
+              PolkitIdentity            *user_for_subject,
+              gboolean                   subject_is_local,
+              gboolean                   subject_is_active,
+              GError                   **error)
+{
+  gboolean ret = FALSE;
+  pid_t pid;
+  uid_t uid;
+  gchar *user_name = NULL;
+  GPtrArray *groups = NULL;
+  struct passwd *passwd;
+  char *seat_str = NULL;
+  char *session_str = NULL;
+
+  duk_get_global_string (cx, "Subject");
+  duk_new (cx, 0);
+
+  if (POLKIT_IS_UNIX_PROCESS (subject))
+    {
+      pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (subject));
+    }
+  else if (POLKIT_IS_SYSTEM_BUS_NAME (subject))
+    {
+      PolkitSubject *process;
+      process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, error);
+      if (process == NULL)
+        goto out;
+      pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (process));
+      g_object_unref (process);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+#ifdef HAVE_LIBSYSTEMD
+  if (sd_pid_get_session (pid, &session_str) == 0)
+    {
+      if (sd_session_get_seat (session_str, &seat_str) == 0)
+        {
+          /* do nothing */
+        }
+    }
+#endif /* HAVE_LIBSYSTEMD */
+
+  g_assert (POLKIT_IS_UNIX_USER (user_for_subject));
+  uid = polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_for_subject));
+
+  groups = g_ptr_array_new_with_free_func (g_free);
+
+  passwd = getpwuid (uid);
+  if (passwd == NULL)
+    {
+      user_name = g_strdup_printf ("%d", (gint) uid);
+      g_warning ("Error looking up info for uid %d: %m", (gint) uid);
+    }
+  else
+    {
+      gid_t gids[512];
+      int num_gids = 512;
+
+      user_name = g_strdup (passwd->pw_name);
+
+      if (getgrouplist (passwd->pw_name,
+                        passwd->pw_gid,
+                        gids,
+                        &num_gids) < 0)
+        {
+          g_warning ("Error looking up groups for uid %d: %m", (gint) uid);
+        }
+      else
+        {
+          gint n;
+          for (n = 0; n < num_gids; n++)
+            {
+              struct group *group;
+              group = getgrgid (gids[n]);
+              if (group == NULL)
+                {
+                  g_ptr_array_add (groups, g_strdup_printf ("%d", (gint) gids[n]));
+                }
+              else
+                {
+                  g_ptr_array_add (groups, g_strdup (group->gr_name));
+                }
+            }
+        }
+    }
+
+  set_property_int32 (cx, "pid", pid);
+  set_property_str (cx, "user", user_name);
+  set_property_strv (cx, "groups", groups);
+  set_property_str (cx, "seat", seat_str);
+  set_property_str (cx, "session", session_str);
+  set_property_bool (cx, "local", subject_is_local);
+  set_property_bool (cx, "active", subject_is_active);
+
+  ret = TRUE;
+
+ out:
+  free (session_str);
+  free (seat_str);
+  g_free (user_name);
+  if (groups != NULL)
+    g_ptr_array_unref (groups);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+push_action_and_details (duk_context               *cx,
+                         const gchar               *action_id,
+                         PolkitDetails             *details,
+                         GError                   **error)
+{
+  gchar **keys;
+  guint n;
+
+  duk_get_global_string (cx, "Action");
+  duk_new (cx, 0);
+
+  set_property_str (cx, "id", action_id);
+
+  keys = polkit_details_get_keys (details);
+  for (n = 0; keys != NULL && keys[n] != NULL; n++)
+    {
+      gchar *key;
+      const gchar *value;
+      key = g_strdup_printf ("_detail_%s", keys[n]);
+      value = polkit_details_lookup (details, keys[n]);
+      set_property_str (cx, key, value);
+      g_free (key);
+    }
+  g_strfreev (keys);
+
+  return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GList *
+polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
+                                                       PolkitSubject                     *caller,
+                                                       PolkitSubject                     *subject,
+                                                       PolkitIdentity                    *user_for_subject,
+                                                       gboolean                           subject_is_local,
+                                                       gboolean                           subject_is_active,
+                                                       const gchar                       *action_id,
+                                                       PolkitDetails                     *details)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
+  GList *ret = NULL;
+  guint n;
+  GError *error = NULL;
+  const char *ret_str = NULL;
+  gchar **ret_strs = NULL;
+  duk_context *cx = authority->priv->cx;
+
+  duk_set_top (cx, 0);
+  duk_get_global_string (cx, "polkit");
+  duk_push_string (cx, "_runAdminRules");
+
+  if (!push_action_and_details (cx, action_id, details, &error))
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error converting action and details to JS object: %s",
+                                    error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error))
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error converting subject to JS object: %s",
+                                    error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE)
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error evaluating admin rules: ",
+                                    duk_safe_to_string (cx, -1));
+      goto out;
+    }
+
+  ret_str = duk_require_string (cx, -1);
+
+  ret_strs = g_strsplit (ret_str, ",", -1);
+  for (n = 0; ret_strs != NULL && ret_strs[n] != NULL; n++)
+    {
+      const gchar *identity_str = ret_strs[n];
+      PolkitIdentity *identity;
+
+      error = NULL;
+      identity = polkit_identity_from_string (identity_str, &error);
+      if (identity == NULL)
+        {
+          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                        "Identity `%s' is not valid, ignoring: %s",
+                                        identity_str, error->message);
+          g_clear_error (&error);
+        }
+      else
+        {
+          ret = g_list_prepend (ret, identity);
+        }
+    }
+  ret = g_list_reverse (ret);
+
+ out:
+  g_strfreev (ret_strs);
+  /* fallback to root password auth */
+  if (ret == NULL)
+    ret = g_list_prepend (ret, polkit_unix_user_new (0));
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static PolkitImplicitAuthorization
+polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
+                                                      PolkitSubject                     *caller,
+                                                      PolkitSubject                     *subject,
+                                                      PolkitIdentity                    *user_for_subject,
+                                                      gboolean                           subject_is_local,
+                                                      gboolean                           subject_is_active,
+                                                      const gchar                       *action_id,
+                                                      PolkitDetails                     *details,
+                                                      PolkitImplicitAuthorization        implicit)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
+  PolkitImplicitAuthorization ret = implicit;
+  GError *error = NULL;
+  gchar *ret_str = NULL;
+  gboolean good = FALSE;
+  duk_context *cx = authority->priv->cx;
+
+  duk_set_top (cx, 0);
+  duk_get_global_string (cx, "polkit");
+  duk_push_string (cx, "_runRules");
+
+  if (!push_action_and_details (cx, action_id, details, &error))
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error converting action and details to JS object: %s",
+                                    error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error))
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error converting subject to JS object: %s",
+                                    error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE)
+  {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error evaluating authorization rules: ",
+                                    duk_safe_to_string (cx, -1));
+      goto out;
+  }
+
+  if (duk_is_null(cx, -1)) {
+    good = TRUE;
+    goto out;
+  }
+  ret_str = g_strdup (duk_require_string (cx, -1));
+  if (!polkit_implicit_authorization_from_string (ret_str, &ret))
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Returned result `%s' is not valid",
+                                    ret_str);
+      goto out;
+    }
+
+  good = TRUE;
+
+ out:
+  if (!good)
+    ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED;
+  g_free (ret_str);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static duk_ret_t
+js_polkit_log (duk_context *cx)
+{
+  const char *str = duk_require_string (cx, 0);
+  fprintf (stderr, "%s\n", str);
+  return 0;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+get_signal_name (gint signal_number)
+{
+  switch (signal_number)
+    {
+#define _HANDLE_SIG(sig) case sig: return #sig;
+    _HANDLE_SIG (SIGHUP);
+    _HANDLE_SIG (SIGINT);
+    _HANDLE_SIG (SIGQUIT);
+    _HANDLE_SIG (SIGILL);
+    _HANDLE_SIG (SIGABRT);
+    _HANDLE_SIG (SIGFPE);
+    _HANDLE_SIG (SIGKILL);
+    _HANDLE_SIG (SIGSEGV);
+    _HANDLE_SIG (SIGPIPE);
+    _HANDLE_SIG (SIGALRM);
+    _HANDLE_SIG (SIGTERM);
+    _HANDLE_SIG (SIGUSR1);
+    _HANDLE_SIG (SIGUSR2);
+    _HANDLE_SIG (SIGCHLD);
+    _HANDLE_SIG (SIGCONT);
+    _HANDLE_SIG (SIGSTOP);
+    _HANDLE_SIG (SIGTSTP);
+    _HANDLE_SIG (SIGTTIN);
+    _HANDLE_SIG (SIGTTOU);
+    _HANDLE_SIG (SIGBUS);
+#ifdef SIGPOLL
+    _HANDLE_SIG (SIGPOLL);
+#endif
+    _HANDLE_SIG (SIGPROF);
+    _HANDLE_SIG (SIGSYS);
+    _HANDLE_SIG (SIGTRAP);
+    _HANDLE_SIG (SIGURG);
+    _HANDLE_SIG (SIGVTALRM);
+    _HANDLE_SIG (SIGXCPU);
+    _HANDLE_SIG (SIGXFSZ);
+#undef _HANDLE_SIG
+    default:
+      break;
+    }
+  return "UNKNOWN_SIGNAL";
+}
+
+typedef struct
+{
+  GMainLoop *loop;
+  GAsyncResult *res;
+} SpawnData;
+
+static void
+spawn_cb (GObject       *source_object,
+          GAsyncResult  *res,
+          gpointer       user_data)
+{
+  SpawnData *data = user_data;
+  data->res = g_object_ref (res);
+  g_main_loop_quit (data->loop);
+}
+
+static duk_ret_t
+js_polkit_spawn (duk_context *cx)
+{
+#if (DUK_VERSION >= 20000)
+  duk_ret_t ret = DUK_RET_ERROR;
+#else
+  duk_ret_t ret = DUK_RET_INTERNAL_ERROR;
+#endif
+  gchar *standard_output = NULL;
+  gchar *standard_error = NULL;
+  gint exit_status;
+  GError *error = NULL;
+  guint32 array_len;
+  gchar **argv = NULL;
+  GMainContext *context = NULL;
+  GMainLoop *loop = NULL;
+  SpawnData data = {0};
+  char *err_str = NULL;
+  guint n;
+
+  if (!duk_is_array (cx, 0))
+    goto out;
+
+  array_len = duk_get_length (cx, 0);
+
+  argv = g_new0 (gchar*, array_len + 1);
+  for (n = 0; n < array_len; n++)
+    {
+      duk_get_prop_index (cx, 0, n);
+      argv[n] = g_strdup (duk_to_string (cx, -1));
+      duk_pop (cx);
+    }
+
+  context = g_main_context_new ();
+  loop = g_main_loop_new (context, FALSE);
+
+  g_main_context_push_thread_default (context);
+
+  data.loop = loop;
+  utils_spawn ((const gchar *const *) argv,
+               10, /* timeout_seconds */
+               NULL, /* cancellable */
+               spawn_cb,
+               &data);
+
+  g_main_loop_run (loop);
+
+  g_main_context_pop_thread_default (context);
+
+  if (!utils_spawn_finish (data.res,
+                           &exit_status,
+                           &standard_output,
+                           &standard_error,
+                           &error))
+    {
+      err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)",
+                                 error->message, g_quark_to_string (error->domain), error->code);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0))
+    {
+      GString *gstr;
+      gstr = g_string_new (NULL);
+      if (WIFEXITED (exit_status))
+        {
+          g_string_append_printf (gstr,
+                                  "Helper exited with non-zero exit status %d",
+                                  WEXITSTATUS (exit_status));
+        }
+      else if (WIFSIGNALED (exit_status))
+        {
+          g_string_append_printf (gstr,
+                                  "Helper was signaled with signal %s (%d)",
+                                  get_signal_name (WTERMSIG (exit_status)),
+                                  WTERMSIG (exit_status));
+        }
+      g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'",
+                              standard_output, standard_error);
+      err_str = g_string_free (gstr, FALSE);
+      goto out;
+    }
+
+  duk_push_string (cx, standard_output);
+  ret = 1;
+
+ out:
+  g_strfreev (argv);
+  g_free (standard_output);
+  g_free (standard_error);
+  g_clear_object (&data.res);
+  if (loop != NULL)
+    g_main_loop_unref (loop);
+  if (context != NULL)
+    g_main_context_unref (context);
+
+  if (err_str)
+    duk_error (cx, DUK_ERR_ERROR, err_str);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static duk_ret_t
+js_polkit_user_is_in_netgroup (duk_context *cx)
+{
+  const char *user;
+  const char *netgroup;
+  gboolean is_in_netgroup = FALSE;
+
+  user = duk_require_string (cx, 0);
+  netgroup = duk_require_string (cx, 1);
+
+  if (innetgr (netgroup,
+               NULL,  /* host */
+               user,
+               NULL)) /* domain */
+    {
+      is_in_netgroup = TRUE;
+    }
+
+  duk_push_boolean (cx, is_in_netgroup);
+  return 1;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GSimpleAsyncResult *simple; /* borrowed reference */
+  GMainContext *main_context; /* may be NULL */
+
+  GCancellable *cancellable;  /* may be NULL */
+  gulong cancellable_handler_id;
+
+  GPid child_pid;
+  gint child_stdout_fd;
+  gint child_stderr_fd;
+
+  GIOChannel *child_stdout_channel;
+  GIOChannel *child_stderr_channel;
+
+  GSource *child_watch_source;
+  GSource *child_stdout_source;
+  GSource *child_stderr_source;
+
+  guint timeout_seconds;
+  gboolean timed_out;
+  GSource *timeout_source;
+
+  GString *child_stdout;
+  GString *child_stderr;
+
+  gint exit_status;
+} UtilsSpawnData;
+
+static void
+utils_child_watch_from_release_cb (GPid     pid,
+                                   gint     status,
+                                   gpointer user_data)
+{
+}
+
+static void
+utils_spawn_data_free (UtilsSpawnData *data)
+{
+  if (data->timeout_source != NULL)
+    {
+      g_source_destroy (data->timeout_source);
+      data->timeout_source = NULL;
+    }
+
+  /* Nuke the child, if necessary */
+  if (data->child_watch_source != NULL)
+    {
+      g_source_destroy (data->child_watch_source);
+      data->child_watch_source = NULL;
+    }
+
+  if (data->child_pid != 0)
+    {
+      GSource *source;
+      kill (data->child_pid, SIGTERM);
+      /* OK, we need to reap for the child ourselves - we don't want
+       * to use waitpid() because that might block the calling
+       * thread (the child might handle SIGTERM and use several
+       * seconds for cleanup/rollback).
+       *
+       * So we use GChildWatch instead.
+       *
+       * Avoid taking a references to ourselves. but note that we need
+       * to pass the GSource so we can nuke it once handled.
+       */
+      source = g_child_watch_source_new (data->child_pid);
+      g_source_set_callback (source,
+                             (GSourceFunc) utils_child_watch_from_release_cb,
+                             source,
+                             (GDestroyNotify) g_source_destroy);
+      g_source_attach (source, data->main_context);
+      g_source_unref (source);
+      data->child_pid = 0;
+    }
+
+  if (data->child_stdout != NULL)
+    {
+      g_string_free (data->child_stdout, TRUE);
+      data->child_stdout = NULL;
+    }
+
+  if (data->child_stderr != NULL)
+    {
+      g_string_free (data->child_stderr, TRUE);
+      data->child_stderr = NULL;
+    }
+
+  if (data->child_stdout_channel != NULL)
+    {
+      g_io_channel_unref (data->child_stdout_channel);
+      data->child_stdout_channel = NULL;
+    }
+  if (data->child_stderr_channel != NULL)
+    {
+      g_io_channel_unref (data->child_stderr_channel);
+      data->child_stderr_channel = NULL;
+    }
+
+  if (data->child_stdout_source != NULL)
+    {
+      g_source_destroy (data->child_stdout_source);
+      data->child_stdout_source = NULL;
+    }
+  if (data->child_stderr_source != NULL)
+    {
+      g_source_destroy (data->child_stderr_source);
+      data->child_stderr_source = NULL;
+    }
+
+  if (data->child_stdout_fd != -1)
+    {
+      g_warn_if_fail (close (data->child_stdout_fd) == 0);
+      data->child_stdout_fd = -1;
+    }
+  if (data->child_stderr_fd != -1)
+    {
+      g_warn_if_fail (close (data->child_stderr_fd) == 0);
+      data->child_stderr_fd = -1;
+    }
+
+  if (data->cancellable_handler_id > 0)
+    {
+      g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+      data->cancellable_handler_id = 0;
+    }
+
+  if (data->main_context != NULL)
+    g_main_context_unref (data->main_context);
+
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+
+  g_slice_free (UtilsSpawnData, data);
+}
+
+/* called in the thread where @cancellable was cancelled */
+static void
+utils_on_cancelled (GCancellable *cancellable,
+                    gpointer      user_data)
+{
+  UtilsSpawnData *data = user_data;
+  GError *error;
+
+  error = NULL;
+  g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
+  g_simple_async_result_take_error (data->simple, error);
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+}
+
+static gboolean
+utils_read_child_stderr (GIOChannel *channel,
+                         GIOCondition condition,
+                         gpointer user_data)
+{
+  UtilsSpawnData *data = user_data;
+  gchar buf[1024];
+  gsize bytes_read;
+
+  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+  g_string_append_len (data->child_stderr, buf, bytes_read);
+  return TRUE;
+}
+
+static gboolean
+utils_read_child_stdout (GIOChannel *channel,
+                         GIOCondition condition,
+                         gpointer user_data)
+{
+  UtilsSpawnData *data = user_data;
+  gchar buf[1024];
+  gsize bytes_read;
+
+  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+  g_string_append_len (data->child_stdout, buf, bytes_read);
+  return TRUE;
+}
+
+static void
+utils_child_watch_cb (GPid     pid,
+                      gint     status,
+                      gpointer user_data)
+{
+  UtilsSpawnData *data = user_data;
+  gchar *buf;
+  gsize buf_size;
+
+  if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+    {
+      g_string_append_len (data->child_stdout, buf, buf_size);
+      g_free (buf);
+    }
+  if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+    {
+      g_string_append_len (data->child_stderr, buf, buf_size);
+      g_free (buf);
+    }
+
+  data->exit_status = status;
+
+  /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
+  data->child_pid = 0;
+  data->child_watch_source = NULL;
+
+  /* we're done */
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+}
+
+static gboolean
+utils_timeout_cb (gpointer user_data)
+{
+  UtilsSpawnData *data = user_data;
+
+  data->timed_out = TRUE;
+
+  /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
+  data->timeout_source = NULL;
+
+  /* we're done */
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+
+  return FALSE; /* remove source */
+}
+
+static void
+utils_spawn (const gchar *const  *argv,
+             guint                timeout_seconds,
+             GCancellable        *cancellable,
+             GAsyncReadyCallback  callback,
+             gpointer             user_data)
+{
+  UtilsSpawnData *data;
+  GError *error;
+
+  data = g_slice_new0 (UtilsSpawnData);
+  data->timeout_seconds = timeout_seconds;
+  data->simple = g_simple_async_result_new (NULL,
+                                            callback,
+                                            user_data,
+                                            utils_spawn);
+  data->main_context = g_main_context_get_thread_default ();
+  if (data->main_context != NULL)
+    g_main_context_ref (data->main_context);
+
+  data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+
+  data->child_stdout = g_string_new (NULL);
+  data->child_stderr = g_string_new (NULL);
+  data->child_stdout_fd = -1;
+  data->child_stderr_fd = -1;
+
+  /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */
+  g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free);
+
+  error = NULL;
+  if (data->cancellable != NULL)
+    {
+      /* could already be cancelled */
+      error = NULL;
+      if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+        {
+          g_simple_async_result_take_error (data->simple, error);
+          g_simple_async_result_complete_in_idle (data->simple);
+          g_object_unref (data->simple);
+          goto out;
+        }
+
+      data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
+                                                            G_CALLBACK (utils_on_cancelled),
+                                                            data,
+                                                            NULL);
+    }
+
+  error = NULL;
+  if (!g_spawn_async_with_pipes (NULL, /* working directory */
+                                 (gchar **) argv,
+                                 NULL, /* envp */
+                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                                 NULL, /* child_setup */
+                                 NULL, /* child_setup's user_data */
+                                 &(data->child_pid),
+                                 NULL, /* gint *stdin_fd */
+                                 &(data->child_stdout_fd),
+                                 &(data->child_stderr_fd),
+                                 &error))
+    {
+      g_prefix_error (&error, "Error spawning: ");
+      g_simple_async_result_take_error (data->simple, error);
+      g_simple_async_result_complete_in_idle (data->simple);
+      g_object_unref (data->simple);
+      goto out;
+    }
+
+  if (timeout_seconds > 0)
+    {
+      data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
+      g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL);
+      g_source_attach (data->timeout_source, data->main_context);
+      g_source_unref (data->timeout_source);
+    }
+
+  data->child_watch_source = g_child_watch_source_new (data->child_pid);
+  g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL);
+  g_source_attach (data->child_watch_source, data->main_context);
+  g_source_unref (data->child_watch_source);
+
+  data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
+  g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
+  data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
+  g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL);
+  g_source_attach (data->child_stdout_source, data->main_context);
+  g_source_unref (data->child_stdout_source);
+
+  data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
+  g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
+  data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
+  g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL);
+  g_source_attach (data->child_stderr_source, data->main_context);
+  g_source_unref (data->child_stderr_source);
+
+ out:
+  ;
+}
+
+gboolean
+utils_spawn_finish (GAsyncResult   *res,
+                    gint           *out_exit_status,
+                    gchar         **out_standard_output,
+                    gchar         **out_standard_error,
+                    GError        **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  UtilsSpawnData *data;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  data = g_simple_async_result_get_op_res_gpointer (simple);
+
+  if (data->timed_out)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_TIMED_OUT,
+                   "Timed out after %d seconds",
+                   data->timeout_seconds);
+      goto out;
+    }
+
+  if (out_exit_status != NULL)
+    *out_exit_status = data->exit_status;
+
+  if (out_standard_output != NULL)
+    *out_standard_output = g_strdup (data->child_stdout->str);
+
+  if (out_standard_error != NULL)
+    *out_standard_error = g_strdup (data->child_stderr->str);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
-- 
GitLab


From d74aad8152a7c51999fffa9abe28e4306a052399 Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 13:15:17 +0800
Subject: [PATCH 02/16] check netgroup.h header file

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index ae98453..543d6fd 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -26,7 +26,11 @@
 #include <errno.h>
 #include <pwd.h>
 #include <grp.h>
+#ifdef HAVE_NETGROUP_H
+#include <netgroup.h>
+#else
 #include <netdb.h>
+#endif
 #include <string.h>
 #include <glib/gstdio.h>
 #include <locale.h>
-- 
GitLab


From 69c761506cbe458807e4ae2742c9e05bc60dad3d Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 10:59:03 +0800
Subject: [PATCH 03/16] check return value

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 543d6fd..a54ed5b 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -249,7 +249,11 @@ reload_scripts (PolkitBackendJsAuthority *authority)
   duk_context *cx = authority->priv->cx;
 
   duk_set_top (cx, 0);
-  duk_get_global_string (cx, "polkit");
+  if (!duk_get_global_string (cx, "polkit")) {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error deleting old rules, not loading new ones");
+      return;
+  }
   duk_push_string (cx, "_deleteRules");
 
   duk_call_prop (cx, 0, 0);
-- 
GitLab


From f1536c4899934fd3c8243fda2d084a472fe57d2e Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 11:22:39 +0800
Subject: [PATCH 04/16] check return value

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index a54ed5b..1a7e6d3 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -656,7 +656,10 @@ push_action_and_details (duk_context               *cx,
   gchar **keys;
   guint n;
 
-  duk_get_global_string (cx, "Action");
+  if (!duk_get_global_string (cx, "Action")) {
+    return FALSE;
+  }
+
   duk_new (cx, 0);
 
   set_property_str (cx, "id", action_id);
@@ -699,7 +702,12 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA
   duk_context *cx = authority->priv->cx;
 
   duk_set_top (cx, 0);
-  duk_get_global_string (cx, "polkit");
+  if (!duk_get_global_string (cx, "polkit")) {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                    "Error deleting old rules, not loading new ones");
+      goto out;
+  }
+
   duk_push_string (cx, "_runAdminRules");
 
   if (!push_action_and_details (cx, action_id, details, &error))
-- 
GitLab


From ca15eecf5dc7755947515c1bfc651fd8770aaf8f Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 13:17:16 +0800
Subject: [PATCH 05/16] check return value

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 1a7e6d3..3f1b32d 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -550,7 +550,10 @@ push_subject (duk_context               *cx,
   char *seat_str = NULL;
   char *session_str = NULL;
 
-  duk_get_global_string (cx, "Subject");
+  if (!duk_get_global_string (cx, "Subject")) {
+    return FALSE;
+  }
+
   duk_new (cx, 0);
 
   if (POLKIT_IS_UNIX_PROCESS (subject))
@@ -789,8 +792,11 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu
   gboolean good = FALSE;
   duk_context *cx = authority->priv->cx;
 
+  if (!duk_get_global_string (cx, "polkit")) {
+      goto out;
+  }
+
   duk_set_top (cx, 0);
-  duk_get_global_string (cx, "polkit");
   duk_push_string (cx, "_runRules");
 
   if (!push_action_and_details (cx, action_id, details, &error))
-- 
GitLab


From 870348365cc0166e14f28e0d144ed552bba4d794 Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 13:18:13 +0800
Subject: [PATCH 06/16] check return value

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 3f1b32d..6294ad9 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -843,7 +843,8 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu
  out:
   if (!good)
     ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED;
-  g_free (ret_str);
+  if (ret_str != NULL)
+      g_free (ret_str);
 
   return ret;
 }
-- 
GitLab


From 81c916ff08fdcee3c7340c4b2d4632086b89666c Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 11:23:04 +0800
Subject: [PATCH 07/16] fix typecase

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 6294ad9..d466c9d 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -1191,7 +1191,7 @@ static void
 utils_on_cancelled (GCancellable *cancellable,
                     gpointer      user_data)
 {
-  UtilsSpawnData *data = user_data;
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
   GError *error;
 
   error = NULL;
@@ -1206,7 +1206,7 @@ utils_read_child_stderr (GIOChannel *channel,
                          GIOCondition condition,
                          gpointer user_data)
 {
-  UtilsSpawnData *data = user_data;
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
   gchar buf[1024];
   gsize bytes_read;
 
@@ -1220,7 +1220,7 @@ utils_read_child_stdout (GIOChannel *channel,
                          GIOCondition condition,
                          gpointer user_data)
 {
-  UtilsSpawnData *data = user_data;
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
   gchar buf[1024];
   gsize bytes_read;
 
@@ -1234,7 +1234,7 @@ utils_child_watch_cb (GPid     pid,
                       gint     status,
                       gpointer user_data)
 {
-  UtilsSpawnData *data = user_data;
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
   gchar *buf;
   gsize buf_size;
 
@@ -1263,7 +1263,7 @@ utils_child_watch_cb (GPid     pid,
 static gboolean
 utils_timeout_cb (gpointer user_data)
 {
-  UtilsSpawnData *data = user_data;
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
 
   data->timed_out = TRUE;
 
-- 
GitLab


From acb956bf52f0a78bf7aaf925876f96e97a146995 Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 18:04:27 +0800
Subject: [PATCH 08/16] typecase

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index d466c9d..237b1ad 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -915,8 +915,8 @@ spawn_cb (GObject       *source_object,
           GAsyncResult  *res,
           gpointer       user_data)
 {
-  SpawnData *data = user_data;
-  data->res = g_object_ref (res);
+  SpawnData *data = (SpawnData *)user_data;
+  data->res = (GAsyncResult*)g_object_ref (res);
   g_main_loop_quit (data->loop);
 }
 
@@ -1292,12 +1292,12 @@ utils_spawn (const gchar *const  *argv,
   data->simple = g_simple_async_result_new (NULL,
                                             callback,
                                             user_data,
-                                            utils_spawn);
+                                            (gpointer*)utils_spawn);
   data->main_context = g_main_context_get_thread_default ();
   if (data->main_context != NULL)
     g_main_context_ref (data->main_context);
 
-  data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+  data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL;
 
   data->child_stdout = g_string_new (NULL);
   data->child_stderr = g_string_new (NULL);
@@ -1397,7 +1397,7 @@ utils_spawn_finish (GAsyncResult   *res,
   if (g_simple_async_result_propagate_error (simple, error))
     goto out;
 
-  data = g_simple_async_result_get_op_res_gpointer (simple);
+  data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple);
 
   if (data->timed_out)
     {
-- 
GitLab


From be060e4d48aceb09af34868b555b6c73c7afdabb Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 13:53:23 +0800
Subject: [PATCH 09/16] some change

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 .../polkitbackendduktapeauthority.c           | 26 +++++++++++--------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 237b1ad..fad9017 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -207,18 +207,22 @@ load_scripts (PolkitBackendJsAuthority  *authority)
 
   for (l = files; l != NULL; l = l->next)
     {
-      const gchar *filename = l->data;
-
+      const gchar *filename = (gchar *)l->data;
 #if (DUK_VERSION >= 20000)
-      gchar *contents;
-      gsize length;
-      GError *error = NULL;
-      if (!g_file_get_contents (filename, &contents, &length, &error)){
-        g_warning("Error when file contents of %s: %s\n", filename, error->message);
-        g_error_free (error);
-        continue;
-      }
-      if (duk_peval_lstring_noresult(cx, contents,length) != 0)
+      GFile *file = g_file_new_for_path (filename);
+      char *contents;
+      gsize len;
+      if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL))
+        {
+          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                        "Error compiling script %s",
+                                        filename);
+          g_object_unref (file);
+          continue;
+        }
+
+      g_object_unref (file);
+      if (duk_peval_lstring_noresult(cx, contents,len) != 0)
 #else
       if (duk_peval_file_noresult (cx, filename) != 0)
 #endif
-- 
GitLab


From 2ffb62048a5ebedfe3bb053feb7385c7270ede28 Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 15:25:45 +0800
Subject: [PATCH 10/16] some change

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 .../polkitbackendduktapeauthority.c           | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index fad9017..6fac3be 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -125,6 +125,18 @@ G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BAC
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static duk_ret_t js_polkit_log (duk_context *cx);
+static duk_ret_t js_polkit_spawn (duk_context *cx);
+static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx);
+
+static const duk_function_list_entry js_polkit_functions[] =
+{
+  { "log", js_polkit_log, 1 },
+  { "spawn", js_polkit_spawn, 1 },
+  { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 },
+  { NULL, NULL, 0 },
+};
+
 static void
 polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
 {
@@ -347,18 +359,6 @@ setup_file_monitors (PolkitBackendJsAuthority *authority)
   authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE);
 }
 
-static duk_ret_t js_polkit_log (duk_context *cx);
-static duk_ret_t js_polkit_spawn (duk_context *cx);
-static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx);
-
-static const duk_function_list_entry js_polkit_functions[] =
-{
-  { "log", js_polkit_log, 1 },
-  { "spawn", js_polkit_spawn, 1 },
-  { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 },
-  { NULL, NULL, 0 },
-};
-
 static void
 polkit_backend_js_authority_constructed (GObject *object)
 {
-- 
GitLab


From edb70ef69eed3275f5654510d135e680eb46c85d Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 15:25:35 +0800
Subject: [PATCH 11/16] remove WATCHDOG_TIMEOUT define

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 6fac3be..51e03fd 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -69,7 +69,6 @@ struct _PolkitBackendJsAuthorityPrivate
   duk_context *cx;
 };
 
-#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND)
 
 static void utils_spawn (const gchar *const  *argv,
                          guint                timeout_seconds,
-- 
GitLab


From 906ae404f29f15ef8c529b999bf091b5d18ed7ac Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 12:46:40 +0800
Subject: [PATCH 12/16] add meson build system support

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 meson.build                   | 11 ++++++++++-
 meson_options.txt             |  1 +
 src/polkitbackend/meson.build | 10 ++++++++--
 3 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/meson.build b/meson.build
index 858078d..4e44723 100644
--- a/meson.build
+++ b/meson.build
@@ -133,7 +133,13 @@ expat_dep = dependency('expat')
 assert(cc.has_header('expat.h', dependencies: expat_dep), 'Can\'t find expat.h. Please install expat.')
 assert(cc.has_function('XML_ParserCreate', dependencies: expat_dep), 'Can\'t find expat library. Please install expat.')
 
-mozjs_dep = dependency('mozjs-78')
+js_engine = get_option('js_engine')
+if js_engine == 'duktape'
+  js_dep = dependency('duktape')
+  libm_dep = cc.find_library('m')
+elif js_engine == 'mozjs'
+  js_dep = dependency('mozjs-78')
+endif
 
 dbus_dep = dependency('dbus-1', required: false)
 dbus_policydir = pk_prefix / pk_datadir / 'dbus-1/system.d'
@@ -361,6 +367,9 @@ if enable_logind
   output += '        systemdsystemunitdir:     ' + systemd_systemdsystemunitdir + '\n'
 endif
 output += '        polkitd user:             ' + polkitd_user + ' \n'
+output += '        Javascript engine:        ' + js_engine + '\n'
+if enable_logind
+endif
 output += '        PAM support:              ' + enable_pam.to_string() + '\n\n'
 if enable_pam
   output += '        PAM file auth:            ' + pam_conf['PAM_FILE_INCLUDE_AUTH'] + '\n'
diff --git a/meson_options.txt b/meson_options.txt
index 25e3e77..76aa311 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -16,3 +16,4 @@ option('introspection', type: 'boolean', value: true, description: 'Enable intro
 
 option('gtk_doc', type: 'boolean', value: false, description: 'use gtk-doc to build documentation')
 option('man', type: 'boolean', value: false, description: 'build manual pages')
+option('js_engine', type: 'combo', choices: ['mozjs', 'duktape'], value: 'duktape', description: 'javascript engine')
diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build
index 64f0e4a..489897d 100644
--- a/src/polkitbackend/meson.build
+++ b/src/polkitbackend/meson.build
@@ -5,7 +5,6 @@ sources = files(
   'polkitbackendactionpool.c',
   'polkitbackendauthority.c',
   'polkitbackendinteractiveauthority.c',
-  'polkitbackendjsauthority.cpp',
 )
 
 output = 'initjs.h'
@@ -21,7 +20,7 @@ sources += custom_target(
 deps = [
   expat_dep,
   libpolkit_gobject_dep,
-  mozjs_dep,
+  js_dep,
 ]
 
 c_flags = [
@@ -31,6 +30,13 @@ c_flags = [
   '-DPACKAGE_SYSCONF_DIR="@0@"'.format(pk_prefix / pk_sysconfdir),
 ]
 
+if js_engine == 'duktape'
+  sources += files('polkitbackendduktapeauthority.c')
+  deps += libm_dep
+elif js_engine == 'mozjs'
+  sources += files('polkitbackendjsauthority.cpp')
+endif
+
 if enable_logind
   sources += files('polkitbackendsessionmonitor-systemd.c')
 
-- 
GitLab


From 1380b505c25be4aebe54b1b4223a570d64af83cc Mon Sep 17 00:00:00 2001
From: Wu Xiaotian <yetist@gmail.com>
Date: Sun, 22 Nov 2020 18:49:14 +0800
Subject: [PATCH 13/16] fix run error

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/polkitbackendduktapeauthority.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 51e03fd..4b4f8fd 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -795,11 +795,11 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu
   gboolean good = FALSE;
   duk_context *cx = authority->priv->cx;
 
+  duk_set_top (cx, 0);
   if (!duk_get_global_string (cx, "polkit")) {
       goto out;
   }
 
-  duk_set_top (cx, 0);
   duk_push_string (cx, "_runRules");
 
   if (!push_action_and_details (cx, action_id, details, &error))
-- 
GitLab


From 6856a704b70378948ef5f66e9b09555d97d4070b Mon Sep 17 00:00:00 2001
From: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
Date: Fri, 10 Sep 2021 15:17:58 -0700
Subject: [PATCH 14/16] Deduplicate code for "Add duktape as JS engine backend"
 effort/MR

This leverages Wu Xiaotian (@yetist)'s original MR
(https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/35), in
an effort to complete said work.

This is the first of the requests from maintainers--to reduce
eliminate code duplication.

The runaway-killer missing functionality will come in the sequence.

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 src/polkitbackend/Makefile.am                 |   1 +
 src/polkitbackend/meson.build                 |   1 +
 src/polkitbackend/polkitbackendcommon.c       | 530 +++++++++++++
 src/polkitbackend/polkitbackendcommon.h       | 156 ++++
 .../polkitbackendduktapeauthority.c           | 714 ++----------------
 .../polkitbackendjsauthority.cpp              | 711 ++---------------
 6 files changed, 790 insertions(+), 1323 deletions(-)
 create mode 100644 src/polkitbackend/polkitbackendcommon.c
 create mode 100644 src/polkitbackend/polkitbackendcommon.h

diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am
index abcbc6f..6a8b4ae 100644
--- a/src/polkitbackend/Makefile.am
+++ b/src/polkitbackend/Makefile.am
@@ -31,6 +31,7 @@ libpolkit_backend_1_la_SOURCES =                                   			\
         polkitbackend.h									\
 	polkitbackendtypes.h								\
 	polkitbackendprivate.h								\
+	polkitbackendcommon.h			polkitbackendcommon.c			\
 	polkitbackendauthority.h		polkitbackendauthority.c		\
 	polkitbackendinteractiveauthority.h	polkitbackendinteractiveauthority.c	\
 	polkitbackendjsauthority.h				\
diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build
index 489897d..9ec01b2 100644
--- a/src/polkitbackend/meson.build
+++ b/src/polkitbackend/meson.build
@@ -4,6 +4,7 @@ sources = files(
   'polkitbackendactionlookup.c',
   'polkitbackendactionpool.c',
   'polkitbackendauthority.c',
+  'polkitbackendcommon.c',
   'polkitbackendinteractiveauthority.c',
 )
 
diff --git a/src/polkitbackend/polkitbackendcommon.c b/src/polkitbackend/polkitbackendcommon.c
new file mode 100644
index 0000000..6783dff
--- /dev/null
+++ b/src/polkitbackend/polkitbackendcommon.c
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "polkitbackendcommon.h"
+
+static void
+utils_child_watch_from_release_cb (GPid     pid,
+                                   gint     status,
+                                   gpointer user_data)
+{
+}
+
+static void
+utils_spawn_data_free (UtilsSpawnData *data)
+{
+  if (data->timeout_source != NULL)
+    {
+      g_source_destroy (data->timeout_source);
+      data->timeout_source = NULL;
+    }
+
+  /* Nuke the child, if necessary */
+  if (data->child_watch_source != NULL)
+    {
+      g_source_destroy (data->child_watch_source);
+      data->child_watch_source = NULL;
+    }
+
+  if (data->child_pid != 0)
+    {
+      GSource *source;
+      kill (data->child_pid, SIGTERM);
+      /* OK, we need to reap for the child ourselves - we don't want
+       * to use waitpid() because that might block the calling
+       * thread (the child might handle SIGTERM and use several
+       * seconds for cleanup/rollback).
+       *
+       * So we use GChildWatch instead.
+       *
+       * Avoid taking a references to ourselves. but note that we need
+       * to pass the GSource so we can nuke it once handled.
+       */
+      source = g_child_watch_source_new (data->child_pid);
+      g_source_set_callback (source,
+                             (GSourceFunc) utils_child_watch_from_release_cb,
+                             source,
+                             (GDestroyNotify) g_source_destroy);
+      g_source_attach (source, data->main_context);
+      g_source_unref (source);
+      data->child_pid = 0;
+    }
+
+  if (data->child_stdout != NULL)
+    {
+      g_string_free (data->child_stdout, TRUE);
+      data->child_stdout = NULL;
+    }
+
+  if (data->child_stderr != NULL)
+    {
+      g_string_free (data->child_stderr, TRUE);
+      data->child_stderr = NULL;
+    }
+
+  if (data->child_stdout_channel != NULL)
+    {
+      g_io_channel_unref (data->child_stdout_channel);
+      data->child_stdout_channel = NULL;
+    }
+  if (data->child_stderr_channel != NULL)
+    {
+      g_io_channel_unref (data->child_stderr_channel);
+      data->child_stderr_channel = NULL;
+    }
+
+  if (data->child_stdout_source != NULL)
+    {
+      g_source_destroy (data->child_stdout_source);
+      data->child_stdout_source = NULL;
+    }
+  if (data->child_stderr_source != NULL)
+    {
+      g_source_destroy (data->child_stderr_source);
+      data->child_stderr_source = NULL;
+    }
+
+  if (data->child_stdout_fd != -1)
+    {
+      g_warn_if_fail (close (data->child_stdout_fd) == 0);
+      data->child_stdout_fd = -1;
+    }
+  if (data->child_stderr_fd != -1)
+    {
+      g_warn_if_fail (close (data->child_stderr_fd) == 0);
+      data->child_stderr_fd = -1;
+    }
+
+  if (data->cancellable_handler_id > 0)
+    {
+      g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+      data->cancellable_handler_id = 0;
+    }
+
+  if (data->main_context != NULL)
+    g_main_context_unref (data->main_context);
+
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+
+  g_slice_free (UtilsSpawnData, data);
+}
+
+/* called in the thread where @cancellable was cancelled */
+static void
+utils_on_cancelled (GCancellable *cancellable,
+                    gpointer      user_data)
+{
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
+  GError *error;
+
+  error = NULL;
+  g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
+  g_simple_async_result_take_error (data->simple, error);
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+}
+
+static gboolean
+utils_timeout_cb (gpointer user_data)
+{
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
+
+  data->timed_out = TRUE;
+
+  /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
+  data->timeout_source = NULL;
+
+  /* we're done */
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+
+  return FALSE; /* remove source */
+}
+
+static void
+utils_child_watch_cb (GPid     pid,
+                      gint     status,
+                      gpointer user_data)
+{
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
+  gchar *buf;
+  gsize buf_size;
+
+  if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+    {
+      g_string_append_len (data->child_stdout, buf, buf_size);
+      g_free (buf);
+    }
+  if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+    {
+      g_string_append_len (data->child_stderr, buf, buf_size);
+      g_free (buf);
+    }
+
+  data->exit_status = status;
+
+  /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
+  data->child_pid = 0;
+  data->child_watch_source = NULL;
+
+  /* we're done */
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+}
+
+static gboolean
+utils_read_child_stderr (GIOChannel *channel,
+                         GIOCondition condition,
+                         gpointer user_data)
+{
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
+  gchar buf[1024];
+  gsize bytes_read;
+
+  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+  g_string_append_len (data->child_stderr, buf, bytes_read);
+  return TRUE;
+}
+
+static gboolean
+utils_read_child_stdout (GIOChannel *channel,
+                         GIOCondition condition,
+                         gpointer user_data)
+{
+  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
+  gchar buf[1024];
+  gsize bytes_read;
+
+  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+  g_string_append_len (data->child_stdout, buf, bytes_read);
+  return TRUE;
+}
+
+void
+polkit_backend_common_spawn (const gchar *const  *argv,
+                             guint                timeout_seconds,
+                             GCancellable        *cancellable,
+                             GAsyncReadyCallback  callback,
+                             gpointer             user_data)
+{
+  UtilsSpawnData *data;
+  GError *error;
+
+  data = g_slice_new0 (UtilsSpawnData);
+  data->timeout_seconds = timeout_seconds;
+  data->simple = g_simple_async_result_new (NULL,
+                                            callback,
+                                            user_data,
+                                            (gpointer*)polkit_backend_common_spawn);
+  data->main_context = g_main_context_get_thread_default ();
+  if (data->main_context != NULL)
+    g_main_context_ref (data->main_context);
+
+  data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL;
+
+  data->child_stdout = g_string_new (NULL);
+  data->child_stderr = g_string_new (NULL);
+  data->child_stdout_fd = -1;
+  data->child_stderr_fd = -1;
+
+  /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */
+  g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free);
+
+  error = NULL;
+  if (data->cancellable != NULL)
+    {
+      /* could already be cancelled */
+      error = NULL;
+      if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+        {
+          g_simple_async_result_take_error (data->simple, error);
+          g_simple_async_result_complete_in_idle (data->simple);
+          g_object_unref (data->simple);
+          goto out;
+        }
+
+      data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
+                                                            G_CALLBACK (utils_on_cancelled),
+                                                            data,
+                                                            NULL);
+    }
+
+  error = NULL;
+  if (!g_spawn_async_with_pipes (NULL, /* working directory */
+                                 (gchar **) argv,
+                                 NULL, /* envp */
+                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                                 NULL, /* child_setup */
+                                 NULL, /* child_setup's user_data */
+                                 &(data->child_pid),
+                                 NULL, /* gint *stdin_fd */
+                                 &(data->child_stdout_fd),
+                                 &(data->child_stderr_fd),
+                                 &error))
+    {
+      g_prefix_error (&error, "Error spawning: ");
+      g_simple_async_result_take_error (data->simple, error);
+      g_simple_async_result_complete_in_idle (data->simple);
+      g_object_unref (data->simple);
+      goto out;
+    }
+
+  if (timeout_seconds > 0)
+    {
+      data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
+      g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL);
+      g_source_attach (data->timeout_source, data->main_context);
+      g_source_unref (data->timeout_source);
+    }
+
+  data->child_watch_source = g_child_watch_source_new (data->child_pid);
+  g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL);
+  g_source_attach (data->child_watch_source, data->main_context);
+  g_source_unref (data->child_watch_source);
+
+  data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
+  g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
+  data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
+  g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL);
+  g_source_attach (data->child_stdout_source, data->main_context);
+  g_source_unref (data->child_stdout_source);
+
+  data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
+  g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
+  data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
+  g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL);
+  g_source_attach (data->child_stderr_source, data->main_context);
+  g_source_unref (data->child_stderr_source);
+
+ out:
+  ;
+}
+
+void
+polkit_backend_common_on_dir_monitor_changed (GFileMonitor     *monitor,
+                                              GFile            *file,
+                                              GFile            *other_file,
+                                              GFileMonitorEvent event_type,
+                                              gpointer          user_data)
+{
+  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
+
+  /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution?
+   *       Because when editing a file with emacs we get 4-8 events..
+   */
+
+  if (file != NULL)
+    {
+      gchar *name;
+
+      name = g_file_get_basename (file);
+
+      /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */
+      if (!g_str_has_prefix (name, ".") &&
+          !g_str_has_prefix (name, "#") &&
+          g_str_has_suffix (name, ".rules") &&
+          (event_type == G_FILE_MONITOR_EVENT_CREATED ||
+           event_type == G_FILE_MONITOR_EVENT_DELETED ||
+           event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT))
+        {
+          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                        "Reloading rules");
+          polkit_backend_common_reload_scripts (authority);
+        }
+      g_free (name);
+    }
+}
+
+gboolean
+polkit_backend_common_spawn_finish (GAsyncResult   *res,
+                                    gint           *out_exit_status,
+                                    gchar         **out_standard_output,
+                                    gchar         **out_standard_error,
+                                    GError        **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  UtilsSpawnData *data;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == polkit_backend_common_spawn);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple);
+
+  if (data->timed_out)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_TIMED_OUT,
+                   "Timed out after %d seconds",
+                   data->timeout_seconds);
+      goto out;
+    }
+
+  if (out_exit_status != NULL)
+    *out_exit_status = data->exit_status;
+
+  if (out_standard_output != NULL)
+    *out_standard_output = g_strdup (data->child_stdout->str);
+
+  if (out_standard_error != NULL)
+    *out_standard_error = g_strdup (data->child_stderr->str);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static const gchar *
+polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority)
+{
+  return "js";
+}
+
+static const gchar *
+polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority)
+{
+  return PACKAGE_VERSION;
+}
+
+static PolkitAuthorityFeatures
+polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority)
+{
+  return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION;
+}
+
+void
+polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass)
+{
+  GObjectClass *gobject_class;
+  PolkitBackendAuthorityClass *authority_class;
+  PolkitBackendInteractiveAuthorityClass *interactive_authority_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize                               = polkit_backend_common_js_authority_finalize;
+  gobject_class->set_property                           = polkit_backend_common_js_authority_set_property;
+  gobject_class->constructed                            = polkit_backend_common_js_authority_constructed;
+
+  authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass);
+  authority_class->get_name                             = polkit_backend_js_authority_get_name;
+  authority_class->get_version                          = polkit_backend_js_authority_get_version;
+  authority_class->get_features                         = polkit_backend_js_authority_get_features;
+
+  interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass);
+  interactive_authority_class->get_admin_identities     = polkit_backend_common_js_authority_get_admin_auth_identities;
+  interactive_authority_class->check_authorization_sync = polkit_backend_common_js_authority_check_authorization_sync;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_RULES_DIRS,
+                                   g_param_spec_boxed ("rules-dirs",
+                                                       NULL,
+                                                       NULL,
+                                                       G_TYPE_STRV,
+                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+gint
+polkit_backend_common_rules_file_name_cmp (const gchar *a,
+                                           const gchar *b)
+{
+  gint ret;
+  const gchar *a_base;
+  const gchar *b_base;
+
+  a_base = strrchr (a, '/');
+  b_base = strrchr (b, '/');
+
+  g_assert (a_base != NULL);
+  g_assert (b_base != NULL);
+  a_base += 1;
+  b_base += 1;
+
+  ret = g_strcmp0 (a_base, b_base);
+  if (ret == 0)
+    {
+      /* /etc wins over /usr */
+      ret = g_strcmp0 (a, b);
+      g_assert (ret != 0);
+    }
+
+  return ret;
+}
+
+const gchar *
+polkit_backend_common_get_signal_name (gint signal_number)
+{
+  switch (signal_number)
+    {
+#define _HANDLE_SIG(sig) case sig: return #sig;
+    _HANDLE_SIG (SIGHUP);
+    _HANDLE_SIG (SIGINT);
+    _HANDLE_SIG (SIGQUIT);
+    _HANDLE_SIG (SIGILL);
+    _HANDLE_SIG (SIGABRT);
+    _HANDLE_SIG (SIGFPE);
+    _HANDLE_SIG (SIGKILL);
+    _HANDLE_SIG (SIGSEGV);
+    _HANDLE_SIG (SIGPIPE);
+    _HANDLE_SIG (SIGALRM);
+    _HANDLE_SIG (SIGTERM);
+    _HANDLE_SIG (SIGUSR1);
+    _HANDLE_SIG (SIGUSR2);
+    _HANDLE_SIG (SIGCHLD);
+    _HANDLE_SIG (SIGCONT);
+    _HANDLE_SIG (SIGSTOP);
+    _HANDLE_SIG (SIGTSTP);
+    _HANDLE_SIG (SIGTTIN);
+    _HANDLE_SIG (SIGTTOU);
+    _HANDLE_SIG (SIGBUS);
+#ifdef SIGPOLL
+    _HANDLE_SIG (SIGPOLL);
+#endif
+    _HANDLE_SIG (SIGPROF);
+    _HANDLE_SIG (SIGSYS);
+    _HANDLE_SIG (SIGTRAP);
+    _HANDLE_SIG (SIGURG);
+    _HANDLE_SIG (SIGVTALRM);
+    _HANDLE_SIG (SIGXCPU);
+    _HANDLE_SIG (SIGXFSZ);
+#undef _HANDLE_SIG
+    default:
+      break;
+    }
+  return "UNKNOWN_SIGNAL";
+}
+
+void
+polkit_backend_common_spawn_cb (GObject       *source_object,
+                                GAsyncResult  *res,
+                                gpointer       user_data)
+{
+  SpawnData *data = (SpawnData *)user_data;
+  data->res = (GAsyncResult*)g_object_ref (res);
+  g_main_loop_quit (data->loop);
+}
diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h
new file mode 100644
index 0000000..6d0d267
--- /dev/null
+++ b/src/polkitbackend/polkitbackendcommon.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (_POLKIT_BACKEND_COMPILATION) && !defined(_POLKIT_BACKEND_INSIDE_POLKIT_BACKEND_H)
+#error "Only <polkitbackend/polkitbackend.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __POLKIT_BACKEND_COMMON_H
+#define __POLKIT_BACKEND_COMMON_H
+
+#include "config.h"
+#include <sys/wait.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#ifdef HAVE_NETGROUP_H
+#include <netgroup.h>
+#else
+#include <netdb.h>
+#endif
+#include <string.h>
+#include <glib/gstdio.h>
+#include <locale.h>
+#include <glib/gi18n-lib.h> //here, all things glib via glib.h (including -> gspawn.h)
+
+#include <polkit/polkit.h>
+#include "polkitbackendjsauthority.h"
+
+#include <polkit/polkitprivate.h>
+
+#ifdef HAVE_LIBSYSTEMD
+#include <systemd/sd-login.h>
+#endif /* HAVE_LIBSYSTEMD */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum
+{
+  PROP_0,
+  PROP_RULES_DIRS,
+};
+
+typedef struct
+{
+  GSimpleAsyncResult *simple; /* borrowed reference */
+  GMainContext *main_context; /* may be NULL */
+
+  GCancellable *cancellable;  /* may be NULL */
+  gulong cancellable_handler_id;
+
+  GPid child_pid;
+  gint child_stdout_fd;
+  gint child_stderr_fd;
+
+  GIOChannel *child_stdout_channel;
+  GIOChannel *child_stderr_channel;
+
+  GSource *child_watch_source;
+  GSource *child_stdout_source;
+  GSource *child_stderr_source;
+
+  guint timeout_seconds;
+  gboolean timed_out;
+  GSource *timeout_source;
+
+  GString *child_stdout;
+  GString *child_stderr;
+
+  gint exit_status;
+} UtilsSpawnData;
+
+typedef struct
+{
+  GMainLoop *loop;
+  GAsyncResult *res;
+} SpawnData;
+
+void polkit_backend_common_spawn (const gchar *const  *argv,
+                                  guint                timeout_seconds,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data);
+void polkit_backend_common_spawn_cb (GObject       *source_object,
+                                     GAsyncResult  *res,
+                                     gpointer       user_data);
+gboolean polkit_backend_common_spawn_finish (GAsyncResult   *res,
+                                             gint           *out_exit_status,
+                                             gchar         **out_standard_output,
+                                             gchar         **out_standard_error,
+                                             GError        **error);
+
+void polkit_backend_common_on_dir_monitor_changed (GFileMonitor     *monitor,
+                                                   GFile            *file,
+                                                   GFile            *other_file,
+                                                   GFileMonitorEvent event_type,
+                                                   gpointer          user_data);
+
+void polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass);
+
+gint polkit_backend_common_rules_file_name_cmp (const gchar *a,
+                                                const gchar *b);
+
+const gchar *polkit_backend_common_get_signal_name (gint signal_number);
+
+/* To be provided by each JS backend, from here onwards  ---------------------------------------------- */
+
+void polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority);
+void polkit_backend_common_js_authority_finalize (GObject *object);
+void polkit_backend_common_js_authority_constructed (GObject *object);
+GList *polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
+                                                                     PolkitSubject                     *caller,
+                                                                     PolkitSubject                     *subject,
+                                                                     PolkitIdentity                    *user_for_subject,
+                                                                     gboolean                           subject_is_local,
+                                                                     gboolean                           subject_is_active,
+                                                                     const gchar                       *action_id,
+                                                                     PolkitDetails                     *details);
+void polkit_backend_common_js_authority_set_property (GObject      *object,
+                                                      guint         property_id,
+                                                      const GValue *value,
+                                                      GParamSpec   *pspec);
+PolkitImplicitAuthorization polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
+                                                                                         PolkitSubject                     *caller,
+                                                                                         PolkitSubject                     *subject,
+                                                                                         PolkitIdentity                    *user_for_subject,
+                                                                                         gboolean                           subject_is_local,
+                                                                                         gboolean                           subject_is_active,
+                                                                                         const gchar                       *action_id,
+                                                                                         PolkitDetails                     *details,
+                                                                                         PolkitImplicitAuthorization        implicit);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __POLKIT_BACKEND_COMMON_H */
+
diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index 4b4f8fd..a2b4420 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -21,32 +21,12 @@
  * Author: David Zeuthen <davidz@redhat.com>
  */
 
-#include "config.h"
-#include <sys/wait.h>
-#include <errno.h>
-#include <pwd.h>
-#include <grp.h>
-#ifdef HAVE_NETGROUP_H
-#include <netgroup.h>
-#else
-#include <netdb.h>
-#endif
-#include <string.h>
-#include <glib/gstdio.h>
-#include <locale.h>
-#include <glib/gi18n-lib.h>
-
-#include <polkit/polkit.h>
-#include "polkitbackendjsauthority.h"
-
-#include <polkit/polkitprivate.h>
+#include "polkitbackendcommon.h"
 
-#ifdef HAVE_LIBSYSTEMD
-#include <systemd/sd-login.h>
-#endif /* HAVE_LIBSYSTEMD */
+#include "duktape.h"
 
+/* Built source and not too big to worry about deduplication */
 #include "initjs.h" /* init.js */
-#include "duktape.h"
 
 /**
  * SECTION:polkitbackendjsauthority
@@ -54,10 +34,9 @@
  * @short_description: JS Authority
  * @stability: Unstable
  *
- * An implementation of #PolkitBackendAuthority that reads and
- * evalates Javascript files and supports interaction with
- * authentication agents (virtue of being based on
- * #PolkitBackendInteractiveAuthority).
+ * An (Duktape-based) implementation of #PolkitBackendAuthority that reads and
+ * evaluates Javascript files and supports interaction with authentication
+ * agents (virtue of being based on #PolkitBackendInteractiveAuthority).
  */
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -66,64 +45,16 @@ struct _PolkitBackendJsAuthorityPrivate
 {
   gchar **rules_dirs;
   GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */
-  duk_context *cx;
-};
-
-
-static void utils_spawn (const gchar *const  *argv,
-                         guint                timeout_seconds,
-                         GCancellable        *cancellable,
-                         GAsyncReadyCallback  callback,
-                         gpointer             user_data);
-
-gboolean utils_spawn_finish (GAsyncResult   *res,
-                             gint           *out_exit_status,
-                             gchar         **out_standard_output,
-                             gchar         **out_standard_error,
-                             GError        **error);
 
-static void on_dir_monitor_changed (GFileMonitor     *monitor,
-                                    GFile            *file,
-                                    GFile            *other_file,
-                                    GFileMonitorEvent event_type,
-                                    gpointer          user_data);
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-enum
-{
-  PROP_0,
-  PROP_RULES_DIRS,
+  duk_context *cx;
 };
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority,
-                                                                     PolkitSubject                     *caller,
-                                                                     PolkitSubject                     *subject,
-                                                                     PolkitIdentity                    *user_for_subject,
-                                                                     gboolean                           subject_is_local,
-                                                                     gboolean                           subject_is_active,
-                                                                     const gchar                       *action_id,
-                                                                     PolkitDetails                     *details);
-
-static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync (
-                                                          PolkitBackendInteractiveAuthority *authority,
-                                                          PolkitSubject                     *caller,
-                                                          PolkitSubject                     *subject,
-                                                          PolkitIdentity                    *user_for_subject,
-                                                          gboolean                           subject_is_local,
-                                                          gboolean                           subject_is_active,
-                                                          const gchar                       *action_id,
-                                                          PolkitDetails                     *details,
-                                                          PolkitImplicitAuthorization        implicit);
-
 G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY);
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-/* ---------------------------------------------------------------------------------------------------- */
-
 static duk_ret_t js_polkit_log (duk_context *cx);
 static duk_ret_t js_polkit_spawn (duk_context *cx);
 static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx);
@@ -144,33 +75,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
                                                  PolkitBackendJsAuthorityPrivate);
 }
 
-static gint
-rules_file_name_cmp (const gchar *a,
-                     const gchar *b)
-{
-  gint ret;
-  const gchar *a_base;
-  const gchar *b_base;
-
-  a_base = strrchr (a, '/');
-  b_base = strrchr (b, '/');
-
-  g_assert (a_base != NULL);
-  g_assert (b_base != NULL);
-  a_base += 1;
-  b_base += 1;
-
-  ret = g_strcmp0 (a_base, b_base);
-  if (ret == 0)
-    {
-      /* /etc wins over /usr */
-      ret = g_strcmp0 (a, b);
-      g_assert (ret != 0);
-    }
-
-  return ret;
-}
-
 static void
 load_scripts (PolkitBackendJsAuthority  *authority)
 {
@@ -214,7 +118,7 @@ load_scripts (PolkitBackendJsAuthority  *authority)
         }
     }
 
-  files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp);
+  files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp);
 
   for (l = files; l != NULL; l = l->next)
     {
@@ -258,8 +162,8 @@ load_scripts (PolkitBackendJsAuthority  *authority)
   g_list_free_full (files, g_free);
 }
 
-static void
-reload_scripts (PolkitBackendJsAuthority *authority)
+void
+polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority)
 {
   duk_context *cx = authority->priv->cx;
 
@@ -282,42 +186,6 @@ reload_scripts (PolkitBackendJsAuthority *authority)
   g_signal_emit_by_name (authority, "changed");
 }
 
-static void
-on_dir_monitor_changed (GFileMonitor     *monitor,
-                        GFile            *file,
-                        GFile            *other_file,
-                        GFileMonitorEvent event_type,
-                        gpointer          user_data)
-{
-  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
-
-  /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution?
-   *       Because when editing a file with emacs we get 4-8 events..
-   */
-
-  if (file != NULL)
-    {
-      gchar *name;
-
-      name = g_file_get_basename (file);
-
-      /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */
-      if (!g_str_has_prefix (name, ".") &&
-          !g_str_has_prefix (name, "#") &&
-          g_str_has_suffix (name, ".rules") &&
-          (event_type == G_FILE_MONITOR_EVENT_CREATED ||
-           event_type == G_FILE_MONITOR_EVENT_DELETED ||
-           event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT))
-        {
-          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
-                                        "Reloading rules");
-          reload_scripts (authority);
-        }
-      g_free (name);
-    }
-}
-
-
 static void
 setup_file_monitors (PolkitBackendJsAuthority *authority)
 {
@@ -349,7 +217,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority)
         {
           g_signal_connect (monitor,
                             "changed",
-                            G_CALLBACK (on_dir_monitor_changed),
+                            G_CALLBACK (polkit_backend_common_on_dir_monitor_changed),
                             authority);
           g_ptr_array_add (p, monitor);
         }
@@ -358,8 +226,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority)
   authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE);
 }
 
-static void
-polkit_backend_js_authority_constructed (GObject *object)
+void
+polkit_backend_common_js_authority_constructed (GObject *object)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
   duk_context *cx;
@@ -395,8 +263,8 @@ polkit_backend_js_authority_constructed (GObject *object)
   g_assert_not_reached ();
 }
 
-static void
-polkit_backend_js_authority_finalize (GObject *object)
+void
+polkit_backend_common_js_authority_finalize (GObject *object)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
   guint n;
@@ -405,7 +273,7 @@ polkit_backend_js_authority_finalize (GObject *object)
     {
       GFileMonitor *monitor = authority->priv->dir_monitors[n];
       g_signal_handlers_disconnect_by_func (monitor,
-                                            G_CALLBACK (on_dir_monitor_changed),
+                                            G_CALLBACK (polkit_backend_common_on_dir_monitor_changed),
                                             authority);
       g_object_unref (monitor);
     }
@@ -417,11 +285,11 @@ polkit_backend_js_authority_finalize (GObject *object)
   G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object);
 }
 
-static void
-polkit_backend_js_authority_set_property (GObject      *object,
-                                          guint         property_id,
-                                          const GValue *value,
-                                          GParamSpec   *pspec)
+void
+polkit_backend_common_js_authority_set_property (GObject      *object,
+                                                 guint         property_id,
+                                                 const GValue *value,
+                                                 GParamSpec   *pspec)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
 
@@ -438,55 +306,10 @@ polkit_backend_js_authority_set_property (GObject      *object,
     }
 }
 
-static const gchar *
-polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority)
-{
-  return "js";
-}
-
-static const gchar *
-polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority)
-{
-  return PACKAGE_VERSION;
-}
-
-static PolkitAuthorityFeatures
-polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority)
-{
-  return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION;
-}
-
 static void
 polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass)
 {
-  GObjectClass *gobject_class;
-  PolkitBackendAuthorityClass *authority_class;
-  PolkitBackendInteractiveAuthorityClass *interactive_authority_class;
-
-
-  gobject_class = G_OBJECT_CLASS (klass);
-  gobject_class->finalize                               = polkit_backend_js_authority_finalize;
-  gobject_class->set_property                           = polkit_backend_js_authority_set_property;
-  gobject_class->constructed                            = polkit_backend_js_authority_constructed;
-
-  authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass);
-  authority_class->get_name                             = polkit_backend_js_authority_get_name;
-  authority_class->get_version                          = polkit_backend_js_authority_get_version;
-  authority_class->get_features                         = polkit_backend_js_authority_get_features;
-
-  interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass);
-  interactive_authority_class->get_admin_identities     = polkit_backend_js_authority_get_admin_auth_identities;
-  interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync;
-
-  g_object_class_install_property (gobject_class,
-                                   PROP_RULES_DIRS,
-                                   g_param_spec_boxed ("rules-dirs",
-                                                       NULL,
-                                                       NULL,
-                                                       G_TYPE_STRV,
-                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
-
-
+  polkit_backend_common_js_authority_class_init_common (klass);
   g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate));
 }
 
@@ -689,15 +512,15 @@ push_action_and_details (duk_context               *cx,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static GList *
-polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
-                                                       PolkitSubject                     *caller,
-                                                       PolkitSubject                     *subject,
-                                                       PolkitIdentity                    *user_for_subject,
-                                                       gboolean                           subject_is_local,
-                                                       gboolean                           subject_is_active,
-                                                       const gchar                       *action_id,
-                                                       PolkitDetails                     *details)
+GList *
+polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
+                                                              PolkitSubject                     *caller,
+                                                              PolkitSubject                     *subject,
+                                                              PolkitIdentity                    *user_for_subject,
+                                                              gboolean                           subject_is_local,
+                                                              gboolean                           subject_is_active,
+                                                              const gchar                       *action_id,
+                                                              PolkitDetails                     *details)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
   GList *ret = NULL;
@@ -777,16 +600,16 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static PolkitImplicitAuthorization
-polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
-                                                      PolkitSubject                     *caller,
-                                                      PolkitSubject                     *subject,
-                                                      PolkitIdentity                    *user_for_subject,
-                                                      gboolean                           subject_is_local,
-                                                      gboolean                           subject_is_active,
-                                                      const gchar                       *action_id,
-                                                      PolkitDetails                     *details,
-                                                      PolkitImplicitAuthorization        implicit)
+PolkitImplicitAuthorization
+polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
+                                                             PolkitSubject                     *caller,
+                                                             PolkitSubject                     *subject,
+                                                             PolkitIdentity                    *user_for_subject,
+                                                             gboolean                           subject_is_local,
+                                                             gboolean                           subject_is_active,
+                                                             const gchar                       *action_id,
+                                                             PolkitDetails                     *details,
+                                                             PolkitImplicitAuthorization        implicit)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
   PolkitImplicitAuthorization ret = implicit;
@@ -864,65 +687,6 @@ js_polkit_log (duk_context *cx)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static const gchar *
-get_signal_name (gint signal_number)
-{
-  switch (signal_number)
-    {
-#define _HANDLE_SIG(sig) case sig: return #sig;
-    _HANDLE_SIG (SIGHUP);
-    _HANDLE_SIG (SIGINT);
-    _HANDLE_SIG (SIGQUIT);
-    _HANDLE_SIG (SIGILL);
-    _HANDLE_SIG (SIGABRT);
-    _HANDLE_SIG (SIGFPE);
-    _HANDLE_SIG (SIGKILL);
-    _HANDLE_SIG (SIGSEGV);
-    _HANDLE_SIG (SIGPIPE);
-    _HANDLE_SIG (SIGALRM);
-    _HANDLE_SIG (SIGTERM);
-    _HANDLE_SIG (SIGUSR1);
-    _HANDLE_SIG (SIGUSR2);
-    _HANDLE_SIG (SIGCHLD);
-    _HANDLE_SIG (SIGCONT);
-    _HANDLE_SIG (SIGSTOP);
-    _HANDLE_SIG (SIGTSTP);
-    _HANDLE_SIG (SIGTTIN);
-    _HANDLE_SIG (SIGTTOU);
-    _HANDLE_SIG (SIGBUS);
-#ifdef SIGPOLL
-    _HANDLE_SIG (SIGPOLL);
-#endif
-    _HANDLE_SIG (SIGPROF);
-    _HANDLE_SIG (SIGSYS);
-    _HANDLE_SIG (SIGTRAP);
-    _HANDLE_SIG (SIGURG);
-    _HANDLE_SIG (SIGVTALRM);
-    _HANDLE_SIG (SIGXCPU);
-    _HANDLE_SIG (SIGXFSZ);
-#undef _HANDLE_SIG
-    default:
-      break;
-    }
-  return "UNKNOWN_SIGNAL";
-}
-
-typedef struct
-{
-  GMainLoop *loop;
-  GAsyncResult *res;
-} SpawnData;
-
-static void
-spawn_cb (GObject       *source_object,
-          GAsyncResult  *res,
-          gpointer       user_data)
-{
-  SpawnData *data = (SpawnData *)user_data;
-  data->res = (GAsyncResult*)g_object_ref (res);
-  g_main_loop_quit (data->loop);
-}
-
 static duk_ret_t
 js_polkit_spawn (duk_context *cx)
 {
@@ -962,21 +726,21 @@ js_polkit_spawn (duk_context *cx)
   g_main_context_push_thread_default (context);
 
   data.loop = loop;
-  utils_spawn ((const gchar *const *) argv,
-               10, /* timeout_seconds */
-               NULL, /* cancellable */
-               spawn_cb,
-               &data);
+  polkit_backend_common_spawn ((const gchar *const *) argv,
+                               10, /* timeout_seconds */
+                               NULL, /* cancellable */
+                               polkit_backend_common_spawn_cb,
+                               &data);
 
   g_main_loop_run (loop);
 
   g_main_context_pop_thread_default (context);
 
-  if (!utils_spawn_finish (data.res,
-                           &exit_status,
-                           &standard_output,
-                           &standard_error,
-                           &error))
+  if (!polkit_backend_common_spawn_finish (data.res,
+                                           &exit_status,
+                                           &standard_output,
+                                           &standard_error,
+                                           &error))
     {
       err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)",
                                  error->message, g_quark_to_string (error->domain), error->code);
@@ -998,7 +762,7 @@ js_polkit_spawn (duk_context *cx)
         {
           g_string_append_printf (gstr,
                                   "Helper was signaled with signal %s (%d)",
-                                  get_signal_name (WTERMSIG (exit_status)),
+                                  polkit_backend_common_get_signal_name (WTERMSIG (exit_status)),
                                   WTERMSIG (exit_status));
         }
       g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'",
@@ -1052,377 +816,3 @@ js_polkit_user_is_in_netgroup (duk_context *cx)
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
-
-typedef struct
-{
-  GSimpleAsyncResult *simple; /* borrowed reference */
-  GMainContext *main_context; /* may be NULL */
-
-  GCancellable *cancellable;  /* may be NULL */
-  gulong cancellable_handler_id;
-
-  GPid child_pid;
-  gint child_stdout_fd;
-  gint child_stderr_fd;
-
-  GIOChannel *child_stdout_channel;
-  GIOChannel *child_stderr_channel;
-
-  GSource *child_watch_source;
-  GSource *child_stdout_source;
-  GSource *child_stderr_source;
-
-  guint timeout_seconds;
-  gboolean timed_out;
-  GSource *timeout_source;
-
-  GString *child_stdout;
-  GString *child_stderr;
-
-  gint exit_status;
-} UtilsSpawnData;
-
-static void
-utils_child_watch_from_release_cb (GPid     pid,
-                                   gint     status,
-                                   gpointer user_data)
-{
-}
-
-static void
-utils_spawn_data_free (UtilsSpawnData *data)
-{
-  if (data->timeout_source != NULL)
-    {
-      g_source_destroy (data->timeout_source);
-      data->timeout_source = NULL;
-    }
-
-  /* Nuke the child, if necessary */
-  if (data->child_watch_source != NULL)
-    {
-      g_source_destroy (data->child_watch_source);
-      data->child_watch_source = NULL;
-    }
-
-  if (data->child_pid != 0)
-    {
-      GSource *source;
-      kill (data->child_pid, SIGTERM);
-      /* OK, we need to reap for the child ourselves - we don't want
-       * to use waitpid() because that might block the calling
-       * thread (the child might handle SIGTERM and use several
-       * seconds for cleanup/rollback).
-       *
-       * So we use GChildWatch instead.
-       *
-       * Avoid taking a references to ourselves. but note that we need
-       * to pass the GSource so we can nuke it once handled.
-       */
-      source = g_child_watch_source_new (data->child_pid);
-      g_source_set_callback (source,
-                             (GSourceFunc) utils_child_watch_from_release_cb,
-                             source,
-                             (GDestroyNotify) g_source_destroy);
-      g_source_attach (source, data->main_context);
-      g_source_unref (source);
-      data->child_pid = 0;
-    }
-
-  if (data->child_stdout != NULL)
-    {
-      g_string_free (data->child_stdout, TRUE);
-      data->child_stdout = NULL;
-    }
-
-  if (data->child_stderr != NULL)
-    {
-      g_string_free (data->child_stderr, TRUE);
-      data->child_stderr = NULL;
-    }
-
-  if (data->child_stdout_channel != NULL)
-    {
-      g_io_channel_unref (data->child_stdout_channel);
-      data->child_stdout_channel = NULL;
-    }
-  if (data->child_stderr_channel != NULL)
-    {
-      g_io_channel_unref (data->child_stderr_channel);
-      data->child_stderr_channel = NULL;
-    }
-
-  if (data->child_stdout_source != NULL)
-    {
-      g_source_destroy (data->child_stdout_source);
-      data->child_stdout_source = NULL;
-    }
-  if (data->child_stderr_source != NULL)
-    {
-      g_source_destroy (data->child_stderr_source);
-      data->child_stderr_source = NULL;
-    }
-
-  if (data->child_stdout_fd != -1)
-    {
-      g_warn_if_fail (close (data->child_stdout_fd) == 0);
-      data->child_stdout_fd = -1;
-    }
-  if (data->child_stderr_fd != -1)
-    {
-      g_warn_if_fail (close (data->child_stderr_fd) == 0);
-      data->child_stderr_fd = -1;
-    }
-
-  if (data->cancellable_handler_id > 0)
-    {
-      g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
-      data->cancellable_handler_id = 0;
-    }
-
-  if (data->main_context != NULL)
-    g_main_context_unref (data->main_context);
-
-  if (data->cancellable != NULL)
-    g_object_unref (data->cancellable);
-
-  g_slice_free (UtilsSpawnData, data);
-}
-
-/* called in the thread where @cancellable was cancelled */
-static void
-utils_on_cancelled (GCancellable *cancellable,
-                    gpointer      user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  GError *error;
-
-  error = NULL;
-  g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
-  g_simple_async_result_take_error (data->simple, error);
-  g_simple_async_result_complete_in_idle (data->simple);
-  g_object_unref (data->simple);
-}
-
-static gboolean
-utils_read_child_stderr (GIOChannel *channel,
-                         GIOCondition condition,
-                         gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  gchar buf[1024];
-  gsize bytes_read;
-
-  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
-  g_string_append_len (data->child_stderr, buf, bytes_read);
-  return TRUE;
-}
-
-static gboolean
-utils_read_child_stdout (GIOChannel *channel,
-                         GIOCondition condition,
-                         gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  gchar buf[1024];
-  gsize bytes_read;
-
-  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
-  g_string_append_len (data->child_stdout, buf, bytes_read);
-  return TRUE;
-}
-
-static void
-utils_child_watch_cb (GPid     pid,
-                      gint     status,
-                      gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  gchar *buf;
-  gsize buf_size;
-
-  if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
-    {
-      g_string_append_len (data->child_stdout, buf, buf_size);
-      g_free (buf);
-    }
-  if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
-    {
-      g_string_append_len (data->child_stderr, buf, buf_size);
-      g_free (buf);
-    }
-
-  data->exit_status = status;
-
-  /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
-  data->child_pid = 0;
-  data->child_watch_source = NULL;
-
-  /* we're done */
-  g_simple_async_result_complete_in_idle (data->simple);
-  g_object_unref (data->simple);
-}
-
-static gboolean
-utils_timeout_cb (gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-
-  data->timed_out = TRUE;
-
-  /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
-  data->timeout_source = NULL;
-
-  /* we're done */
-  g_simple_async_result_complete_in_idle (data->simple);
-  g_object_unref (data->simple);
-
-  return FALSE; /* remove source */
-}
-
-static void
-utils_spawn (const gchar *const  *argv,
-             guint                timeout_seconds,
-             GCancellable        *cancellable,
-             GAsyncReadyCallback  callback,
-             gpointer             user_data)
-{
-  UtilsSpawnData *data;
-  GError *error;
-
-  data = g_slice_new0 (UtilsSpawnData);
-  data->timeout_seconds = timeout_seconds;
-  data->simple = g_simple_async_result_new (NULL,
-                                            callback,
-                                            user_data,
-                                            (gpointer*)utils_spawn);
-  data->main_context = g_main_context_get_thread_default ();
-  if (data->main_context != NULL)
-    g_main_context_ref (data->main_context);
-
-  data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL;
-
-  data->child_stdout = g_string_new (NULL);
-  data->child_stderr = g_string_new (NULL);
-  data->child_stdout_fd = -1;
-  data->child_stderr_fd = -1;
-
-  /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */
-  g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free);
-
-  error = NULL;
-  if (data->cancellable != NULL)
-    {
-      /* could already be cancelled */
-      error = NULL;
-      if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
-        {
-          g_simple_async_result_take_error (data->simple, error);
-          g_simple_async_result_complete_in_idle (data->simple);
-          g_object_unref (data->simple);
-          goto out;
-        }
-
-      data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
-                                                            G_CALLBACK (utils_on_cancelled),
-                                                            data,
-                                                            NULL);
-    }
-
-  error = NULL;
-  if (!g_spawn_async_with_pipes (NULL, /* working directory */
-                                 (gchar **) argv,
-                                 NULL, /* envp */
-                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
-                                 NULL, /* child_setup */
-                                 NULL, /* child_setup's user_data */
-                                 &(data->child_pid),
-                                 NULL, /* gint *stdin_fd */
-                                 &(data->child_stdout_fd),
-                                 &(data->child_stderr_fd),
-                                 &error))
-    {
-      g_prefix_error (&error, "Error spawning: ");
-      g_simple_async_result_take_error (data->simple, error);
-      g_simple_async_result_complete_in_idle (data->simple);
-      g_object_unref (data->simple);
-      goto out;
-    }
-
-  if (timeout_seconds > 0)
-    {
-      data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
-      g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
-      g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL);
-      g_source_attach (data->timeout_source, data->main_context);
-      g_source_unref (data->timeout_source);
-    }
-
-  data->child_watch_source = g_child_watch_source_new (data->child_pid);
-  g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL);
-  g_source_attach (data->child_watch_source, data->main_context);
-  g_source_unref (data->child_watch_source);
-
-  data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
-  g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
-  data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
-  g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL);
-  g_source_attach (data->child_stdout_source, data->main_context);
-  g_source_unref (data->child_stdout_source);
-
-  data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
-  g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
-  data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
-  g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL);
-  g_source_attach (data->child_stderr_source, data->main_context);
-  g_source_unref (data->child_stderr_source);
-
- out:
-  ;
-}
-
-gboolean
-utils_spawn_finish (GAsyncResult   *res,
-                    gint           *out_exit_status,
-                    gchar         **out_standard_output,
-                    gchar         **out_standard_error,
-                    GError        **error)
-{
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
-  UtilsSpawnData *data;
-  gboolean ret = FALSE;
-
-  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn);
-
-  if (g_simple_async_result_propagate_error (simple, error))
-    goto out;
-
-  data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple);
-
-  if (data->timed_out)
-    {
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_TIMED_OUT,
-                   "Timed out after %d seconds",
-                   data->timeout_seconds);
-      goto out;
-    }
-
-  if (out_exit_status != NULL)
-    *out_exit_status = data->exit_status;
-
-  if (out_standard_output != NULL)
-    *out_standard_output = g_strdup (data->child_stdout->str);
-
-  if (out_standard_error != NULL)
-    *out_standard_error = g_strdup (data->child_stderr->str);
-
-  ret = TRUE;
-
- out:
-  return ret;
-}
diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp
index ca17108..e28091d 100644
--- a/src/polkitbackend/polkitbackendjsauthority.cpp
+++ b/src/polkitbackend/polkitbackendjsauthority.cpp
@@ -19,29 +19,7 @@
  * Author: David Zeuthen <davidz@redhat.com>
  */
 
-#include "config.h"
-#include <sys/wait.h>
-#include <errno.h>
-#include <pwd.h>
-#include <grp.h>
-#ifdef HAVE_NETGROUP_H
-#include <netgroup.h>
-#else
-#include <netdb.h>
-#endif
-#include <string.h>
-#include <glib/gstdio.h>
-#include <locale.h>
-#include <glib/gi18n-lib.h>
-
-#include <polkit/polkit.h>
-#include "polkitbackendjsauthority.h"
-
-#include <polkit/polkitprivate.h>
-
-#ifdef HAVE_LIBSYSTEMD
-#include <systemd/sd-login.h>
-#endif /* HAVE_LIBSYSTEMD */
+#include "polkitbackendcommon.h"
 
 #include <js/CompilationAndEvaluation.h>
 #include <js/ContextOptions.h>
@@ -52,6 +30,7 @@
 #include <js/Array.h>
 #include <jsapi.h>
 
+/* Built source and not too big to worry about deduplication */
 #include "initjs.h" /* init.js */
 
 #ifdef JSGC_USE_EXACT_ROOTING
@@ -67,10 +46,9 @@
  * @short_description: JS Authority
  * @stability: Unstable
  *
- * An implementation of #PolkitBackendAuthority that reads and
- * evalates Javascript files and supports interaction with
- * authentication agents (virtue of being based on
- * #PolkitBackendInteractiveAuthority).
+ * An (SpiderMonkey-based) implementation of #PolkitBackendAuthority that reads
+ * and evaluates Javascript files and supports interaction with authentication
+ * agents (virtue of being based on #PolkitBackendInteractiveAuthority).
  */
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -100,57 +78,11 @@ static bool execute_script_with_runaway_killer (PolkitBackendJsAuthority *author
                                     JS::HandleScript                 script,
                                     JS::MutableHandleValue           rval);
 
-static void utils_spawn (const gchar *const  *argv,
-                         guint                timeout_seconds,
-                         GCancellable        *cancellable,
-                         GAsyncReadyCallback  callback,
-                         gpointer             user_data);
-
-gboolean utils_spawn_finish (GAsyncResult   *res,
-                             gint           *out_exit_status,
-                             gchar         **out_standard_output,
-                             gchar         **out_standard_error,
-                             GError        **error);
-
-static void on_dir_monitor_changed (GFileMonitor     *monitor,
-                                    GFile            *file,
-                                    GFile            *other_file,
-                                    GFileMonitorEvent event_type,
-                                    gpointer          user_data);
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-enum
-{
-  PROP_0,
-  PROP_RULES_DIRS,
-};
-
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gpointer runaway_killer_thread_func (gpointer user_data);
 static void runaway_killer_terminate (PolkitBackendJsAuthority *authority);
 
-static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority,
-                                                                     PolkitSubject                     *caller,
-                                                                     PolkitSubject                     *subject,
-                                                                     PolkitIdentity                    *user_for_subject,
-                                                                     gboolean                           subject_is_local,
-                                                                     gboolean                           subject_is_active,
-                                                                     const gchar                       *action_id,
-                                                                     PolkitDetails                     *details);
-
-static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync (
-                                                          PolkitBackendInteractiveAuthority *authority,
-                                                          PolkitSubject                     *caller,
-                                                          PolkitSubject                     *subject,
-                                                          PolkitIdentity                    *user_for_subject,
-                                                          gboolean                           subject_is_local,
-                                                          gboolean                           subject_is_active,
-                                                          const gchar                       *action_id,
-                                                          PolkitDetails                     *details,
-                                                          PolkitImplicitAuthorization        implicit);
-
 G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY);
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -229,33 +161,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
                                                  PolkitBackendJsAuthorityPrivate);
 }
 
-static gint
-rules_file_name_cmp (const gchar *a,
-                     const gchar *b)
-{
-  gint ret;
-  const gchar *a_base;
-  const gchar *b_base;
-
-  a_base = strrchr (a, '/');
-  b_base = strrchr (b, '/');
-
-  g_assert (a_base != NULL);
-  g_assert (b_base != NULL);
-  a_base += 1;
-  b_base += 1;
-
-  ret = g_strcmp0 (a_base, b_base);
-  if (ret == 0)
-    {
-      /* /etc wins over /usr */
-      ret = g_strcmp0 (a, b);
-      g_assert (ret != 0);
-    }
-
-  return ret;
-}
-
 /* authority->priv->cx must be within a request */
 static void
 load_scripts (PolkitBackendJsAuthority  *authority)
@@ -299,7 +204,7 @@ load_scripts (PolkitBackendJsAuthority  *authority)
         }
     }
 
-  files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp);
+  files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp);
 
   for (l = files; l != NULL; l = l->next)
     {
@@ -365,8 +270,8 @@ load_scripts (PolkitBackendJsAuthority  *authority)
   g_list_free_full (files, g_free);
 }
 
-static void
-reload_scripts (PolkitBackendJsAuthority *authority)
+void
+polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority)
 {
   JS::RootedValueArray<1> args(authority->priv->cx);
   JS::RootedValue rval(authority->priv->cx);
@@ -395,42 +300,6 @@ reload_scripts (PolkitBackendJsAuthority *authority)
   g_signal_emit_by_name (authority, "changed");
 }
 
-static void
-on_dir_monitor_changed (GFileMonitor     *monitor,
-                        GFile            *file,
-                        GFile            *other_file,
-                        GFileMonitorEvent event_type,
-                        gpointer          user_data)
-{
-  PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
-
-  /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution?
-   *       Because when editing a file with emacs we get 4-8 events..
-   */
-
-  if (file != NULL)
-    {
-      gchar *name;
-
-      name = g_file_get_basename (file);
-
-      /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */
-      if (!g_str_has_prefix (name, ".") &&
-          !g_str_has_prefix (name, "#") &&
-          g_str_has_suffix (name, ".rules") &&
-          (event_type == G_FILE_MONITOR_EVENT_CREATED ||
-           event_type == G_FILE_MONITOR_EVENT_DELETED ||
-           event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT))
-        {
-          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
-                                        "Reloading rules");
-          reload_scripts (authority);
-        }
-      g_free (name);
-    }
-}
-
-
 static void
 setup_file_monitors (PolkitBackendJsAuthority *authority)
 {
@@ -462,7 +331,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority)
         {
           g_signal_connect (monitor,
                             "changed",
-                            G_CALLBACK (on_dir_monitor_changed),
+                            G_CALLBACK (polkit_backend_common_on_dir_monitor_changed),
                             authority);
           g_ptr_array_add (p, monitor);
         }
@@ -471,8 +340,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority)
   authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE);
 }
 
-static void
-polkit_backend_js_authority_constructed (GObject *object)
+void
+polkit_backend_common_js_authority_constructed (GObject *object)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
 
@@ -561,8 +430,8 @@ polkit_backend_js_authority_constructed (GObject *object)
   g_assert_not_reached ();
 }
 
-static void
-polkit_backend_js_authority_finalize (GObject *object)
+void
+polkit_backend_common_js_authority_finalize (GObject *object)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
   guint n;
@@ -577,7 +446,7 @@ polkit_backend_js_authority_finalize (GObject *object)
     {
       GFileMonitor *monitor = authority->priv->dir_monitors[n];
       g_signal_handlers_disconnect_by_func (monitor,
-                                            (gpointer*)G_CALLBACK (on_dir_monitor_changed),
+                                            (gpointer*)G_CALLBACK (polkit_backend_common_on_dir_monitor_changed),
                                             authority);
       g_object_unref (monitor);
     }
@@ -594,11 +463,11 @@ polkit_backend_js_authority_finalize (GObject *object)
   G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object);
 }
 
-static void
-polkit_backend_js_authority_set_property (GObject      *object,
-                                          guint         property_id,
-                                          const GValue *value,
-                                          GParamSpec   *pspec)
+void
+polkit_backend_common_js_authority_set_property (GObject      *object,
+                                                 guint         property_id,
+                                                 const GValue *value,
+                                                 GParamSpec   *pspec)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
 
@@ -615,57 +484,12 @@ polkit_backend_js_authority_set_property (GObject      *object,
     }
 }
 
-static const gchar *
-polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority)
-{
-  return "js";
-}
-
-static const gchar *
-polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority)
-{
-  return PACKAGE_VERSION;
-}
-
-static PolkitAuthorityFeatures
-polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority)
-{
-  return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION;
-}
-
 static void
 polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass)
 {
-  GObjectClass *gobject_class;
-  PolkitBackendAuthorityClass *authority_class;
-  PolkitBackendInteractiveAuthorityClass *interactive_authority_class;
-
-
-  gobject_class = G_OBJECT_CLASS (klass);
-  gobject_class->finalize                               = polkit_backend_js_authority_finalize;
-  gobject_class->set_property                           = polkit_backend_js_authority_set_property;
-  gobject_class->constructed                            = polkit_backend_js_authority_constructed;
-
-  authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass);
-  authority_class->get_name                             = polkit_backend_js_authority_get_name;
-  authority_class->get_version                          = polkit_backend_js_authority_get_version;
-  authority_class->get_features                         = polkit_backend_js_authority_get_features;
-
-  interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass);
-  interactive_authority_class->get_admin_identities     = polkit_backend_js_authority_get_admin_auth_identities;
-  interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync;
-
-  g_object_class_install_property (gobject_class,
-                                   PROP_RULES_DIRS,
-                                   g_param_spec_boxed ("rules-dirs",
-                                                       NULL,
-                                                       NULL,
-                                                       G_TYPE_STRV,
-                                                       GParamFlags(G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)));
-
+  polkit_backend_common_js_authority_class_init_common (klass);
 
   g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate));
-
   JS_Init ();
 }
 
@@ -1099,15 +923,15 @@ call_js_function_with_runaway_killer (PolkitBackendJsAuthority *authority,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static GList *
-polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
-                                                       PolkitSubject                     *caller,
-                                                       PolkitSubject                     *subject,
-                                                       PolkitIdentity                    *user_for_subject,
-                                                       gboolean                           subject_is_local,
-                                                       gboolean                           subject_is_active,
-                                                       const gchar                       *action_id,
-                                                       PolkitDetails                     *details)
+GList *
+polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
+                                                              PolkitSubject                     *caller,
+                                                              PolkitSubject                     *subject,
+                                                              PolkitIdentity                    *user_for_subject,
+                                                              gboolean                           subject_is_local,
+                                                              gboolean                           subject_is_active,
+                                                              const gchar                       *action_id,
+                                                              PolkitDetails                     *details)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
   GList *ret = NULL;
@@ -1202,16 +1026,16 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static PolkitImplicitAuthorization
-polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
-                                                      PolkitSubject                     *caller,
-                                                      PolkitSubject                     *subject,
-                                                      PolkitIdentity                    *user_for_subject,
-                                                      gboolean                           subject_is_local,
-                                                      gboolean                           subject_is_active,
-                                                      const gchar                       *action_id,
-                                                      PolkitDetails                     *details,
-                                                      PolkitImplicitAuthorization        implicit)
+PolkitImplicitAuthorization
+polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
+                                                             PolkitSubject                     *caller,
+                                                             PolkitSubject                     *subject,
+                                                             PolkitIdentity                    *user_for_subject,
+                                                             gboolean                           subject_is_local,
+                                                             gboolean                           subject_is_active,
+                                                             const gchar                       *action_id,
+                                                             PolkitDetails                     *details,
+                                                             PolkitImplicitAuthorization        implicit)
 {
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
   PolkitImplicitAuthorization ret = implicit;
@@ -1324,65 +1148,6 @@ js_polkit_log (JSContext  *cx,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static const gchar *
-get_signal_name (gint signal_number)
-{
-  switch (signal_number)
-    {
-#define _HANDLE_SIG(sig) case sig: return #sig;
-    _HANDLE_SIG (SIGHUP);
-    _HANDLE_SIG (SIGINT);
-    _HANDLE_SIG (SIGQUIT);
-    _HANDLE_SIG (SIGILL);
-    _HANDLE_SIG (SIGABRT);
-    _HANDLE_SIG (SIGFPE);
-    _HANDLE_SIG (SIGKILL);
-    _HANDLE_SIG (SIGSEGV);
-    _HANDLE_SIG (SIGPIPE);
-    _HANDLE_SIG (SIGALRM);
-    _HANDLE_SIG (SIGTERM);
-    _HANDLE_SIG (SIGUSR1);
-    _HANDLE_SIG (SIGUSR2);
-    _HANDLE_SIG (SIGCHLD);
-    _HANDLE_SIG (SIGCONT);
-    _HANDLE_SIG (SIGSTOP);
-    _HANDLE_SIG (SIGTSTP);
-    _HANDLE_SIG (SIGTTIN);
-    _HANDLE_SIG (SIGTTOU);
-    _HANDLE_SIG (SIGBUS);
-#ifdef SIGPOLL
-    _HANDLE_SIG (SIGPOLL);
-#endif
-    _HANDLE_SIG (SIGPROF);
-    _HANDLE_SIG (SIGSYS);
-    _HANDLE_SIG (SIGTRAP);
-    _HANDLE_SIG (SIGURG);
-    _HANDLE_SIG (SIGVTALRM);
-    _HANDLE_SIG (SIGXCPU);
-    _HANDLE_SIG (SIGXFSZ);
-#undef _HANDLE_SIG
-    default:
-      break;
-    }
-  return "UNKNOWN_SIGNAL";
-}
-
-typedef struct
-{
-  GMainLoop *loop;
-  GAsyncResult *res;
-} SpawnData;
-
-static void
-spawn_cb (GObject       *source_object,
-          GAsyncResult  *res,
-          gpointer       user_data)
-{
-  SpawnData *data = (SpawnData *)user_data;
-  data->res = (GAsyncResult*)g_object_ref (res);
-  g_main_loop_quit (data->loop);
-}
-
 static bool
 js_polkit_spawn (JSContext  *cx,
                  unsigned    js_argc,
@@ -1440,21 +1205,21 @@ js_polkit_spawn (JSContext  *cx,
   g_main_context_push_thread_default (context);
 
   data.loop = loop;
-  utils_spawn ((const gchar *const *) argv,
-               10, /* timeout_seconds */
-               NULL, /* cancellable */
-               spawn_cb,
-               &data);
+  polkit_backend_common_spawn ((const gchar *const *) argv,
+                               10, /* timeout_seconds */
+                               NULL, /* cancellable */
+                               polkit_backend_common_spawn_cb,
+                               &data);
 
   g_main_loop_run (loop);
 
   g_main_context_pop_thread_default (context);
 
-  if (!utils_spawn_finish (data.res,
-                           &exit_status,
-                           &standard_output,
-                           &standard_error,
-                           &error))
+  if (!polkit_backend_common_spawn_finish (data.res,
+                                           &exit_status,
+                                           &standard_output,
+                                           &standard_error,
+                                           &error))
     {
       JS_ReportErrorUTF8 (cx,
                       "Error spawning helper: %s (%s, %d)",
@@ -1477,7 +1242,7 @@ js_polkit_spawn (JSContext  *cx,
         {
           g_string_append_printf (gstr,
                                   "Helper was signaled with signal %s (%d)",
-                                  get_signal_name (WTERMSIG (exit_status)),
+                                  polkit_backend_common_get_signal_name (WTERMSIG (exit_status)),
                                   WTERMSIG (exit_status));
         }
       g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'",
@@ -1542,381 +1307,5 @@ js_polkit_user_is_in_netgroup (JSContext  *cx,
   return ret;
 }
 
-
-
 /* ---------------------------------------------------------------------------------------------------- */
 
-typedef struct
-{
-  GSimpleAsyncResult *simple; /* borrowed reference */
-  GMainContext *main_context; /* may be NULL */
-
-  GCancellable *cancellable;  /* may be NULL */
-  gulong cancellable_handler_id;
-
-  GPid child_pid;
-  gint child_stdout_fd;
-  gint child_stderr_fd;
-
-  GIOChannel *child_stdout_channel;
-  GIOChannel *child_stderr_channel;
-
-  GSource *child_watch_source;
-  GSource *child_stdout_source;
-  GSource *child_stderr_source;
-
-  guint timeout_seconds;
-  gboolean timed_out;
-  GSource *timeout_source;
-
-  GString *child_stdout;
-  GString *child_stderr;
-
-  gint exit_status;
-} UtilsSpawnData;
-
-static void
-utils_child_watch_from_release_cb (GPid     pid,
-                                   gint     status,
-                                   gpointer user_data)
-{
-}
-
-static void
-utils_spawn_data_free (UtilsSpawnData *data)
-{
-  if (data->timeout_source != NULL)
-    {
-      g_source_destroy (data->timeout_source);
-      data->timeout_source = NULL;
-    }
-
-  /* Nuke the child, if necessary */
-  if (data->child_watch_source != NULL)
-    {
-      g_source_destroy (data->child_watch_source);
-      data->child_watch_source = NULL;
-    }
-
-  if (data->child_pid != 0)
-    {
-      GSource *source;
-      kill (data->child_pid, SIGTERM);
-      /* OK, we need to reap for the child ourselves - we don't want
-       * to use waitpid() because that might block the calling
-       * thread (the child might handle SIGTERM and use several
-       * seconds for cleanup/rollback).
-       *
-       * So we use GChildWatch instead.
-       *
-       * Avoid taking a references to ourselves. but note that we need
-       * to pass the GSource so we can nuke it once handled.
-       */
-      source = g_child_watch_source_new (data->child_pid);
-      g_source_set_callback (source,
-                             (GSourceFunc) utils_child_watch_from_release_cb,
-                             source,
-                             (GDestroyNotify) g_source_destroy);
-      /* attach source to the global default main context */
-      g_source_attach (source, NULL);
-      g_source_unref (source);
-      data->child_pid = 0;
-    }
-
-  if (data->child_stdout != NULL)
-    {
-      g_string_free (data->child_stdout, TRUE);
-      data->child_stdout = NULL;
-    }
-
-  if (data->child_stderr != NULL)
-    {
-      g_string_free (data->child_stderr, TRUE);
-      data->child_stderr = NULL;
-    }
-
-  if (data->child_stdout_channel != NULL)
-    {
-      g_io_channel_unref (data->child_stdout_channel);
-      data->child_stdout_channel = NULL;
-    }
-  if (data->child_stderr_channel != NULL)
-    {
-      g_io_channel_unref (data->child_stderr_channel);
-      data->child_stderr_channel = NULL;
-    }
-
-  if (data->child_stdout_source != NULL)
-    {
-      g_source_destroy (data->child_stdout_source);
-      data->child_stdout_source = NULL;
-    }
-  if (data->child_stderr_source != NULL)
-    {
-      g_source_destroy (data->child_stderr_source);
-      data->child_stderr_source = NULL;
-    }
-
-  if (data->child_stdout_fd != -1)
-    {
-      g_warn_if_fail (close (data->child_stdout_fd) == 0);
-      data->child_stdout_fd = -1;
-    }
-  if (data->child_stderr_fd != -1)
-    {
-      g_warn_if_fail (close (data->child_stderr_fd) == 0);
-      data->child_stderr_fd = -1;
-    }
-
-  if (data->cancellable_handler_id > 0)
-    {
-      g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
-      data->cancellable_handler_id = 0;
-    }
-
-  if (data->main_context != NULL)
-    g_main_context_unref (data->main_context);
-
-  if (data->cancellable != NULL)
-    g_object_unref (data->cancellable);
-
-  g_slice_free (UtilsSpawnData, data);
-}
-
-/* called in the thread where @cancellable was cancelled */
-static void
-utils_on_cancelled (GCancellable *cancellable,
-                    gpointer      user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  GError *error;
-
-  error = NULL;
-  g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
-  g_simple_async_result_take_error (data->simple, error);
-  g_simple_async_result_complete_in_idle (data->simple);
-  g_object_unref (data->simple);
-}
-
-static gboolean
-utils_read_child_stderr (GIOChannel *channel,
-                         GIOCondition condition,
-                         gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  gchar buf[1024];
-  gsize bytes_read;
-
-  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
-  g_string_append_len (data->child_stderr, buf, bytes_read);
-  return TRUE;
-}
-
-static gboolean
-utils_read_child_stdout (GIOChannel *channel,
-                         GIOCondition condition,
-                         gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  gchar buf[1024];
-  gsize bytes_read;
-
-  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
-  g_string_append_len (data->child_stdout, buf, bytes_read);
-  return TRUE;
-}
-
-static void
-utils_child_watch_cb (GPid     pid,
-                      gint     status,
-                      gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-  gchar *buf;
-  gsize buf_size;
-
-  if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
-    {
-      g_string_append_len (data->child_stdout, buf, buf_size);
-      g_free (buf);
-    }
-  if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
-    {
-      g_string_append_len (data->child_stderr, buf, buf_size);
-      g_free (buf);
-    }
-
-  data->exit_status = status;
-
-  /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
-  data->child_pid = 0;
-  data->child_watch_source = NULL;
-
-  /* we're done */
-  g_simple_async_result_complete_in_idle (data->simple);
-  g_object_unref (data->simple);
-}
-
-static gboolean
-utils_timeout_cb (gpointer user_data)
-{
-  UtilsSpawnData *data = (UtilsSpawnData *)user_data;
-
-  data->timed_out = TRUE;
-
-  /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
-  data->timeout_source = NULL;
-
-  /* we're done */
-  g_simple_async_result_complete_in_idle (data->simple);
-  g_object_unref (data->simple);
-
-  return FALSE; /* remove source */
-}
-
-static void
-utils_spawn (const gchar *const  *argv,
-             guint                timeout_seconds,
-             GCancellable        *cancellable,
-             GAsyncReadyCallback  callback,
-             gpointer             user_data)
-{
-  UtilsSpawnData *data;
-  GError *error;
-
-  data = g_slice_new0 (UtilsSpawnData);
-  data->timeout_seconds = timeout_seconds;
-  data->simple = g_simple_async_result_new (NULL,
-                                            callback,
-                                            user_data,
-                                            (gpointer*)utils_spawn);
-  data->main_context = g_main_context_get_thread_default ();
-  if (data->main_context != NULL)
-    g_main_context_ref (data->main_context);
-
-  data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL;
-
-  data->child_stdout = g_string_new (NULL);
-  data->child_stderr = g_string_new (NULL);
-  data->child_stdout_fd = -1;
-  data->child_stderr_fd = -1;
-
-  /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */
-  g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free);
-
-  error = NULL;
-  if (data->cancellable != NULL)
-    {
-      /* could already be cancelled */
-      error = NULL;
-      if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
-        {
-          g_simple_async_result_take_error (data->simple, error);
-          g_simple_async_result_complete_in_idle (data->simple);
-          g_object_unref (data->simple);
-          goto out;
-        }
-
-      data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
-                                                            G_CALLBACK (utils_on_cancelled),
-                                                            data,
-                                                            NULL);
-    }
-
-  error = NULL;
-  if (!g_spawn_async_with_pipes (NULL, /* working directory */
-                                 (gchar **) argv,
-                                 NULL, /* envp */
-                                 GSpawnFlags(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD),
-                                 NULL, /* child_setup */
-                                 NULL, /* child_setup's user_data */
-                                 &(data->child_pid),
-                                 NULL, /* gint *stdin_fd */
-                                 &(data->child_stdout_fd),
-                                 &(data->child_stderr_fd),
-                                 &error))
-    {
-      g_prefix_error (&error, "Error spawning: ");
-      g_simple_async_result_take_error (data->simple, error);
-      g_simple_async_result_complete_in_idle (data->simple);
-      g_object_unref (data->simple);
-      goto out;
-    }
-
-  if (timeout_seconds > 0)
-    {
-      data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
-      g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
-      g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL);
-      g_source_attach (data->timeout_source, data->main_context);
-      g_source_unref (data->timeout_source);
-    }
-
-  data->child_watch_source = g_child_watch_source_new (data->child_pid);
-  g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL);
-  g_source_attach (data->child_watch_source, data->main_context);
-  g_source_unref (data->child_watch_source);
-
-  data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
-  g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
-  data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
-  g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL);
-  g_source_attach (data->child_stdout_source, data->main_context);
-  g_source_unref (data->child_stdout_source);
-
-  data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
-  g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
-  data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
-  g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL);
-  g_source_attach (data->child_stderr_source, data->main_context);
-  g_source_unref (data->child_stderr_source);
-
- out:
-  ;
-}
-
-gboolean
-utils_spawn_finish (GAsyncResult   *res,
-                    gint           *out_exit_status,
-                    gchar         **out_standard_output,
-                    gchar         **out_standard_error,
-                    GError        **error)
-{
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
-  UtilsSpawnData *data;
-  gboolean ret = FALSE;
-
-  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn);
-
-  if (g_simple_async_result_propagate_error (simple, error))
-    goto out;
-
-  data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple);
-
-  if (data->timed_out)
-    {
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_TIMED_OUT,
-                   "Timed out after %d seconds",
-                   data->timeout_seconds);
-      goto out;
-    }
-
-  if (out_exit_status != NULL)
-    *out_exit_status = data->exit_status;
-
-  if (out_standard_output != NULL)
-    *out_standard_output = g_strdup (data->child_stdout->str);
-
-  if (out_standard_error != NULL)
-    *out_standard_error = g_strdup (data->child_stderr->str);
-
-  ret = TRUE;
-
- out:
-  return ret;
-}
-- 
GitLab


From 4858128107be9c3ab11828ee8f35c5e26efd36ce Mon Sep 17 00:00:00 2001
From: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
Date: Tue, 14 Sep 2021 14:38:15 -0700
Subject: [PATCH 15/16] Gitlab CI: add duktape pkgconfig dependency

Make way for the CI to be able to build with duktape too

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

GitLab


From cd5d6da837fce95f8831a355dad88c83347c7337 Mon Sep 17 00:00:00 2001
From: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
Date: Mon, 20 Sep 2021 17:17:26 -0700
Subject: [PATCH 16/16] duktape: implement runaway scripts killer timeout

This was missing on Duktape's JS backend proposal, now in. As
discussed in
https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/35 and
verified by the commit author, Duktape has no interrupt injection
mechanism (it has no thread-safe API entry whatsoever, even). Using
DUK_USE_EXEC_TIMEOUT_CHECK is also not feasible, because:

  i) It must be enabled at build time and shared object builds of the
  lib on distros go with the default options, something we cannot
  change/control

  ii) That does not account for non-ECMAScript explicit execution
  contexts, like regex execution, native C calls, etc.

It has been agreed, on that thread, that pthread_cond_timedwait()-ing
and having proper Duktape evaluation/execution calls take place in a
separate thread, to be killed after the runaway script killer's
accorded timeout value, a reasonable approach. We have considered
using glib wrappers for direct pthread usage, but that way would make
it impossible to issue
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, ...) and we want to
be paranoid in that regard.

On Duktape, we don't get to err from the JS context (to be captured by
the offending script), but to be forcibly killed on timeout scenarios,
leading to null returns, thus polkit negation, by definition. It's a
reasonable design/compromise.

A fatal error handler routine, for the Duktape context, has also been
added, using the polkit_backend_authority_log() logging infra to
better assist users on what went wrong.

Finally, the script evaluation routine has been made to use
duk_peval_lstring() (previously using _noresult variant), so to able
to present the user with proper error messages, should any occur.

The original runaway script killer test has been adjusted to please
both JS backends.

Signed-off-by: Gustavo Lima Chaves <gustavo.chaves@microsoft.com>
---
 meson.build                                   |   1 +
 src/polkitbackend/meson.build                 |   1 +
 src/polkitbackend/polkitbackendcommon.h       |   2 +
 .../polkitbackendduktapeauthority.c           | 236 ++++++++++++++----
 .../polkitbackendjsauthority.cpp              |  10 +-
 .../etc/polkit-1/rules.d/10-testing.rules     |   6 +-
 .../test-polkitbackendjsauthority.c           |   2 +-
 7 files changed, 209 insertions(+), 49 deletions(-)

diff --git a/meson.build b/meson.build
index 4e44723..46956e3 100644
--- a/meson.build
+++ b/meson.build
@@ -137,6 +137,7 @@ js_engine = get_option('js_engine')
 if js_engine == 'duktape'
   js_dep = dependency('duktape')
   libm_dep = cc.find_library('m')
+  libpthread_dep = cc.find_library('pthread')
 elif js_engine == 'mozjs'
   js_dep = dependency('mozjs-78')
 endif
diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build
index 9ec01b2..4dfea39 100644
--- a/src/polkitbackend/meson.build
+++ b/src/polkitbackend/meson.build
@@ -34,6 +34,7 @@ c_flags = [
 if js_engine == 'duktape'
   sources += files('polkitbackendduktapeauthority.c')
   deps += libm_dep
+  deps += libpthread_dep
 elif js_engine == 'mozjs'
   sources += files('polkitbackendjsauthority.cpp')
 endif
diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h
index 6d0d267..dd700fc 100644
--- a/src/polkitbackend/polkitbackendcommon.h
+++ b/src/polkitbackend/polkitbackendcommon.h
@@ -50,6 +50,8 @@
 #include <systemd/sd-login.h>
 #endif /* HAVE_LIBSYSTEMD */
 
+#define RUNAWAY_KILLER_TIMEOUT (15)
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
index a2b4420..80f1976 100644
--- a/src/polkitbackend/polkitbackendduktapeauthority.c
+++ b/src/polkitbackend/polkitbackendduktapeauthority.c
@@ -47,8 +47,20 @@ struct _PolkitBackendJsAuthorityPrivate
   GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */
 
   duk_context *cx;
+
+  pthread_t runaway_killer_thread;
+};
+
+enum
+{
+  RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET,
+  RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS,
+  RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE,
 };
 
+static gboolean execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority,
+                                                   const gchar *filename);
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY);
@@ -67,6 +79,15 @@ static const duk_function_list_entry js_polkit_functions[] =
   { NULL, NULL, 0 },
 };
 
+static void report_error (void     *udata,
+                          const char *msg)
+{
+    PolkitBackendJsAuthority *authority = udata;
+    polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+                                  "fatal Duktape JS backend error: %s",
+                                  (msg ? msg : "no message"));
+}
+
 static void
 polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
 {
@@ -78,7 +99,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
 static void
 load_scripts (PolkitBackendJsAuthority  *authority)
 {
-  duk_context *cx = authority->priv->cx;
   GList *files = NULL;
   GList *l;
   guint num_scripts = 0;
@@ -123,36 +143,9 @@ load_scripts (PolkitBackendJsAuthority  *authority)
   for (l = files; l != NULL; l = l->next)
     {
       const gchar *filename = (gchar *)l->data;
-#if (DUK_VERSION >= 20000)
-      GFile *file = g_file_new_for_path (filename);
-      char *contents;
-      gsize len;
-      if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL))
-        {
-          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
-                                        "Error compiling script %s",
-                                        filename);
-          g_object_unref (file);
-          continue;
-        }
 
-      g_object_unref (file);
-      if (duk_peval_lstring_noresult(cx, contents,len) != 0)
-#else
-      if (duk_peval_file_noresult (cx, filename) != 0)
-#endif
-        {
-          polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
-                                        "Error compiling script %s: %s",
-                                        filename, duk_safe_to_string (authority->priv->cx, -1));
-#if (DUK_VERSION >= 20000)
-          g_free (contents);
-#endif
+      if (!execute_script_with_runaway_killer(authority, filename))
           continue;
-        }
-#if (DUK_VERSION >= 20000)
-      g_free (contents);
-#endif
       num_scripts++;
     }
 
@@ -232,7 +225,7 @@ polkit_backend_common_js_authority_constructed (GObject *object)
   PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
   duk_context *cx;
 
-  cx = duk_create_heap (NULL, NULL, NULL, authority, NULL);
+  cx = duk_create_heap (NULL, NULL, NULL, authority, report_error);
   if (cx == NULL)
     goto fail;
 
@@ -243,6 +236,9 @@ polkit_backend_common_js_authority_constructed (GObject *object)
   duk_put_function_list (cx, -1, js_polkit_functions);
   duk_put_prop_string (cx, -2, "polkit");
 
+  /* load polkit objects/functions into JS context (e.g. addRule(),
+   * _deleteRules(), _runRules() et al)
+   */
   duk_eval_string (cx, init_js);
 
   if (authority->priv->rules_dirs == NULL)
@@ -510,6 +506,167 @@ push_action_and_details (duk_context               *cx,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+typedef struct {
+  PolkitBackendJsAuthority *authority;
+  const gchar *filename;
+  pthread_cond_t cond;
+  pthread_mutex_t mutex;
+  gint ret;
+} RunawayKillerCtx;
+
+static gpointer
+runaway_killer_thread_execute_js (gpointer user_data)
+{
+  RunawayKillerCtx *ctx = user_data;
+  duk_context *cx = ctx->authority->priv->cx;
+
+  int oldtype;
+
+  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
+
+#if (DUK_VERSION >= 20000)
+  GFile *file = g_file_new_for_path(ctx->filename);
+  char *contents;
+  gsize len;
+
+  if (!g_file_load_contents(file, NULL, &contents, &len, NULL, NULL)) {
+    polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority),
+                                 "Error compiling script %s", ctx->filename);
+    g_object_unref(file);
+    goto err;
+  }
+
+  g_object_unref(file);
+
+  /* evaluate the script, trying to print context in any syntax errors
+     found */
+  if (duk_peval_lstring(cx, contents, len) != 0)
+#else
+  if (duk_peval_file(cx, ctx->filename) != 0)
+#endif
+  {
+    polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority),
+                                 "Error compiling script %s: %s", ctx->filename,
+                                 duk_safe_to_string(cx, -1));
+    duk_pop(cx);
+    goto free_err;
+  }
+#if (DUK_VERSION >= 20000)
+  g_free(contents);
+#endif
+
+  ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS;
+  goto end;
+
+free_err:
+#if (DUK_VERSION >= 20000)
+  g_free(contents);
+#endif
+err:
+  ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE;
+end:
+  pthread_cond_signal(&ctx->cond);
+  return NULL;
+}
+
+static gpointer
+runaway_killer_thread_call_js (gpointer user_data)
+{
+  RunawayKillerCtx *ctx = user_data;
+  duk_context *cx = ctx->authority->priv->cx;
+  int oldtype;
+
+  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
+
+  if (duk_pcall_prop (cx, 0, 2) != DUK_EXEC_SUCCESS)
+    {
+      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority),
+                                    "Error evaluating admin rules: ",
+                                    duk_safe_to_string (cx, -1));
+      goto err;
+    }
+
+  ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS;
+  goto end;
+
+err:
+  ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE;
+end:
+  pthread_cond_signal(&ctx->cond);
+  return NULL;
+}
+
+/* Blocking for at most for RUNAWAY_KILLER_TIMEOUT */
+static gboolean
+execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority,
+                                   const gchar *filename)
+{
+  gint64 end_time;
+  gboolean cancel = FALSE;
+  RunawayKillerCtx ctx = {.authority = authority, .filename = filename,
+                          .ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET,
+                          .mutex = PTHREAD_MUTEX_INITIALIZER,
+                          .cond = PTHREAD_COND_INITIALIZER};
+  struct timespec abs_time;
+
+  pthread_mutex_lock(&ctx.mutex);
+
+  clock_gettime(CLOCK_REALTIME, &abs_time);
+  abs_time.tv_sec += RUNAWAY_KILLER_TIMEOUT;
+
+  pthread_create(&authority->priv->runaway_killer_thread, NULL, runaway_killer_thread_execute_js, &ctx);
+
+  while (ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET) /* loop to treat spurious wakeups */
+    if (pthread_cond_timedwait(&ctx.cond, &ctx.mutex, &abs_time) == ETIMEDOUT) {
+      cancel = TRUE;
+      break;
+    }
+
+  pthread_mutex_unlock(&ctx.mutex);
+
+  if (cancel)
+    pthread_cancel (authority->priv->runaway_killer_thread);
+  pthread_join (authority->priv->runaway_killer_thread, NULL);
+
+  return ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS;
+}
+
+/* Calls already stacked function and args. Blocking for at most for
+ * RUNAWAY_KILLER_TIMEOUT
+ */
+static gboolean
+call_js_function_with_runaway_killer(PolkitBackendJsAuthority *authority)
+{
+  gint64 end_time;
+  gboolean cancel = FALSE;
+  RunawayKillerCtx ctx = {.authority = authority,
+                          .ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET,
+                          .mutex = PTHREAD_MUTEX_INITIALIZER,
+                          .cond = PTHREAD_COND_INITIALIZER};
+  struct timespec abs_time;
+
+  pthread_mutex_lock(&ctx.mutex);
+
+  clock_gettime(CLOCK_REALTIME, &abs_time);
+  abs_time.tv_sec += RUNAWAY_KILLER_TIMEOUT;
+
+  pthread_create(&authority->priv->runaway_killer_thread, NULL, runaway_killer_thread_call_js, &ctx);
+
+  while (ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET) /* loop to treat spurious wakeups */
+    if (pthread_cond_timedwait(&ctx.cond, &ctx.mutex, &abs_time) == ETIMEDOUT) {
+      cancel = TRUE;
+      break;
+    }
+
+  pthread_mutex_unlock(&ctx.mutex);
+
+  if (cancel)
+    pthread_cancel (authority->priv->runaway_killer_thread);
+  pthread_join (authority->priv->runaway_killer_thread, NULL);
+
+  return ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS;
+}
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 GList *
@@ -557,13 +714,8 @@ polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInter
       goto out;
     }
 
-  if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE)
-    {
-      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
-                                    "Error evaluating admin rules: ",
-                                    duk_safe_to_string (cx, -1));
-      goto out;
-    }
+  if (!call_js_function_with_runaway_killer (authority))
+    goto out;
 
   ret_str = duk_require_string (cx, -1);
 
@@ -643,15 +795,11 @@ polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendIntera
       goto out;
     }
 
-  if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE)
-  {
-      polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
-                                    "Error evaluating authorization rules: ",
-                                    duk_safe_to_string (cx, -1));
-      goto out;
-  }
+  if (!call_js_function_with_runaway_killer (authority))
+    goto out;
 
   if (duk_is_null(cx, -1)) {
+    /* this fine, means there was no match, use implicit authorizations */
     good = TRUE;
     goto out;
   }
diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp
index e28091d..11e91c0 100644
--- a/src/polkitbackend/polkitbackendjsauthority.cpp
+++ b/src/polkitbackend/polkitbackendjsauthority.cpp
@@ -829,11 +829,14 @@ runaway_killer_setup (PolkitBackendJsAuthority *authority)
 {
   g_assert (authority->priv->rkt_source == NULL);
 
-  /* set-up timer for runaway scripts, will be executed in runaway_killer_thread */
+  /* set-up timer for runaway scripts, will be executed in
+     runaway_killer_thread, that is one, permanent thread running a glib
+     mainloop (rkt_loop) whose context (rkt_context) has a timeout source
+     (rkt_source) */
   g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex);
   authority->priv->rkt_timeout_pending = FALSE;
   g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
-  authority->priv->rkt_source = g_timeout_source_new_seconds (15);
+  authority->priv->rkt_source = g_timeout_source_new_seconds (RUNAWAY_KILLER_TIMEOUT);
   g_source_set_callback (authority->priv->rkt_source, rkt_on_timeout, authority, NULL);
   g_source_attach (authority->priv->rkt_source, authority->priv->rkt_context);
 
@@ -893,6 +896,9 @@ execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority,
 {
   bool ret;
 
+  // tries to JS_ExecuteScript(), may hang for > RUNAWAY_KILLER_TIMEOUT,
+  // runaway_killer_thread makes sure the call returns, due to exception
+  // injection
   runaway_killer_setup (authority);
   ret = JS_ExecuteScript (authority->priv->cx,
                           script,
diff --git a/test/data/etc/polkit-1/rules.d/10-testing.rules b/test/data/etc/polkit-1/rules.d/10-testing.rules
index 98bf062..e346b5d 100644
--- a/test/data/etc/polkit-1/rules.d/10-testing.rules
+++ b/test/data/etc/polkit-1/rules.d/10-testing.rules
@@ -189,8 +189,10 @@ polkit.addRule(function(action, subject) {
                 ;
         } catch (error) {
             if (error == "Terminating runaway script")
-                return polkit.Result.YES;
-            return polkit.Result.NO;
+                // Inverted logic to accomodate Duktape's model as well, which
+                // will always fail with negation, on timeouts
+                return polkit.Result.NO;
+            return polkit.Result.YES;
         }
     }
 });
diff --git a/test/polkitbackend/test-polkitbackendjsauthority.c b/test/polkitbackend/test-polkitbackendjsauthority.c
index f97e0e0..2103b17 100644
--- a/test/polkitbackend/test-polkitbackendjsauthority.c
+++ b/test/polkitbackend/test-polkitbackendjsauthority.c
@@ -328,7 +328,7 @@ static const RulesTestCase rules_test_cases[] = {
     "net.company.run_away_script",
     "unix-user:root",
     NULL,
-    POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED,
+    POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED,
   },
 
   {
-- 
GitLab

